Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
public class ExtrasBomVerifier extends DynamicBomVerifier {

private static final Set<String> EXTRAS_EXCLUSIONS = Set.of(
"boms/", // BOM test modules themselves
"examples/", // Example applications
"tck/", // TCK test suite
"tests/", // Integration tests
"boms/", // BOM test modules themselves
"examples/", // Example applications
"tck/", // TCK test suite
"tests/", // Integration tests
"test-utils-docker/", // Test utilities for Docker-based tests
"extras/queue-manager-replicated/tests-multi-instance/", // Test harness applications
"extras/queue-manager-replicated/tests-single-instance/", // Test harness applications
"extras/opentelemetry/integration-tests/" // Test harness applications
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
public class ReferenceBomVerifier extends DynamicBomVerifier {

private static final Set<String> REFERENCE_EXCLUSIONS = Set.of(
"boms/", // BOM test modules themselves
"examples/", // Example applications
"tck/", // TCK test suite
"tests/" // Integration tests
"boms/", // BOM test modules themselves
"examples/", // Example applications
"tck/", // TCK test suite
"tests/", // Integration tests
"test-utils-docker/" // Test utilities for Docker-based tests
// Note: reference/ is NOT in this list - we want to verify those classes load
);

Expand Down
6 changes: 6 additions & 0 deletions boms/sdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@
</dependency>

<!-- Test utilities -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-test-utils-docker</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>a2a-java-sdk-tests-server-common</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
public class SdkBomVerifier extends DynamicBomVerifier {

private static final Set<String> SDK_EXCLUSIONS = Set.of(
"boms/", // BOM test modules themselves
"examples/", // Example applications
"tck/", // TCK test suite
"tests/" // Integration tests
"boms/", // BOM test modules themselves
"examples/", // Example applications
"tck/", // TCK test suite
"tests/", // Integration tests
"test-utils-docker/" // Test utilities for Docker-based tests
);

private static final Set<String> SDK_FORBIDDEN = Set.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>

<!-- Docker test utilities -->
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-test-utils-docker</artifactId>
<scope>test</scope>
</dependency>
<!-- Note: testcontainers dependency already present above -->
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import io.a2a.spec.TaskQueryParams;
import io.a2a.spec.TaskState;
import io.a2a.spec.TransportProtocol;
import io.a2a.testutils.docker.RequiresDocker;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
Expand All @@ -47,14 +48,15 @@
/**
* Multi-instance replication test that validates event queue replication
* between two running Quarkus instances using Testcontainers.
*
*
* Test Architecture:
* - Shared KafkaContainer for event replication
* - Shared PostgreSQLContainer for task persistence
* - Two Quarkus application containers (app1 on 8081, app2 on 8082)
* - A2A Client instances to interact with both applications
*/
@Testcontainers
@RequiresDocker
public class MultiInstanceReplicationTest {

private static final String KAFKA_IMAGE = "confluentinc/cp-kafka:7.6.1";
Expand Down
7 changes: 7 additions & 0 deletions extras/queue-manager-replicated/tests-single-instance/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@
<scope>test</scope>
</dependency>

<!-- Docker test utilities -->
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-test-utils-docker</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

<profiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import io.a2a.spec.TaskStatus;
import io.a2a.spec.TaskStatusUpdateEvent;
import io.a2a.spec.TextPart;
import io.a2a.testutils.docker.RequiresDocker;
import io.quarkus.test.junit.QuarkusTest;
import org.eclipse.microprofile.reactive.messaging.Channel;
import org.eclipse.microprofile.reactive.messaging.Emitter;
Expand All @@ -56,6 +57,7 @@
* Tests the full A2A message flow with Kafka replication verification.
*/
@QuarkusTest
@RequiresDocker
public class KafkaReplicationIntegrationTest {

private static final Logger LOGGER = LoggerFactory.getLogger(KafkaReplicationIntegrationTest.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import jakarta.inject.Inject;

import io.a2a.extras.queuemanager.replicated.core.ReplicatedQueueManager;
import io.a2a.testutils.docker.RequiresDocker;
import io.a2a.server.events.QueueManager;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
Expand All @@ -14,6 +15,7 @@
* For full integration testing with Kafka replication, see KafkaReplicationIntegrationTest.
*/
@QuarkusTest
@RequiresDocker
public class ReplicatedQueueManagerTest {

@Inject
Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@
<artifactId>a2a-java-sdk-opentelemetry-client-propagation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-test-utils-docker</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-bom</artifactId>
Expand Down Expand Up @@ -566,6 +571,7 @@
<module>spec</module>
<module>spec-grpc</module>
<module>tck</module>
<module>test-utils-docker</module>
<module>tests/server-common</module>
<module>transport/jsonrpc</module>
<module>transport/grpc</module>
Expand Down
90 changes: 90 additions & 0 deletions test-utils-docker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Docker Test Utilities

JUnit 5 utilities for conditional execution of Docker/Podman-based tests.

## Overview

This module provides a `@RequiresDocker` annotation that conditionally executes tests based on Docker or Podman availability and user preferences. It automatically checks for both Docker and Podman container engines.

## Usage

Add the dependency to your test module:

```xml
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-test-utils-docker</artifactId>
<scope>test</scope>
</dependency>
```

Annotate test classes that require Docker:

```java
import io.a2a.testutils.docker.RequiresDocker;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

@QuarkusTest
@RequiresDocker
public class MyDockerBasedTest {

@Test
public void testSomethingWithDocker() {
// Test that requires Docker (e.g., Testcontainers, Quarkus Dev Services)
}
}
```

## Behavior

| `-DskipDockerTests` | Docker/Podman Available | Behavior |
|---------------------|------------------------|----------|
| Not set | ✅ Yes | **RUN** tests normally |
| Not set | ❌ No | **ABORT** tests with error message |
| `true` | ✅ Yes | **SKIP** tests (shown as disabled) |
| `true` | ❌ No | **SKIP** tests (shown as disabled) |

## Examples

### Run all tests (requires Docker to be running)
```bash
mvn clean install
```

### Skip Docker tests when Docker is not available
```bash
mvn clean install -DskipDockerTests=true
```

### Expected behavior when Docker is NOT available

**Without skip flag:**
```bash
mvn clean install
# Tests ABORT with: "Docker/Podman is not available. Use -DskipDockerTests=true to skip these tests."
```

**With skip flag:**
```bash
mvn clean install -DskipDockerTests=true
# Tests are SKIPPED, build succeeds
# Modules still compile
```

## Implementation Details

- **Container Detection**: Checks for both `docker` and `podman` commands by executing `docker info` or `podman info`
- **JUnit 5 Extension**: Implements `ExecutionCondition` to control test execution
- **Class-Level Only**: Annotation is applied at the class level (not method level)
- **Compilation**: Modules are always compiled regardless of Docker/Podman availability or skip flag
- **No External Dependencies**: Uses only Java standard library for container detection (no Testcontainers dependency)

## Use Cases

This is useful for:
- **CI/CD pipelines** where Docker/Podman may not be available
- **Local development** when container daemon is not running
- **Quarkus Dev Services** tests that automatically start containers
- **Testcontainers-based** integration tests
- **Podman users** who use Podman instead of Docker
33 changes: 33 additions & 0 deletions test-utils-docker/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-parent</artifactId>
<version>1.0.0.Alpha4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>a2a-java-test-utils-docker</artifactId>
<name>A2A Java SDK :: Test Utils :: Docker</name>
<description>Test utilities for conditional Docker-based test execution</description>

<dependencies>
<!-- JUnit 5 for the extension - provided scope (consumer provides) -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>provided</scope>
</dependency>

<!-- SLF4J for logging - provided scope (consumer provides) -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package io.a2a.testutils.docker;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
* Utility class for checking Docker/Podman availability and test skip settings.
*/
public final class DockerAvailability {

private static final Logger LOG = LoggerFactory.getLogger(DockerAvailability.class);
private static final String SKIP_DOCKER_TESTS_PROPERTY = "skipDockerTests";

private static volatile Boolean dockerAvailable = null;

private DockerAvailability() {
// Utility class
}

/**
* Checks if Docker or Podman is available in the current environment.
* The result is cached after the first check.
*
* @return true if Docker or Podman is available, false otherwise
*/
public static boolean isDockerAvailable() {
if (dockerAvailable == null) {
synchronized (DockerAvailability.class) {
if (dockerAvailable == null) {
dockerAvailable = checkDockerAvailable();
}
}
}
return dockerAvailable;
}

/**
* Checks if Docker tests should be skipped based on the system property.
* Tests are skipped when the system property "skipDockerTests" is set to "true".
*
* @return true if Docker tests should be skipped, false otherwise
*/
public static boolean shouldSkipDockerTests() {
return Boolean.parseBoolean(System.getProperty(SKIP_DOCKER_TESTS_PROPERTY, "false"));
}

private static boolean checkDockerAvailable() {
// Try docker first, then podman
if (tryContainerCommand("docker")) {
LOG.info("Docker is available");
return true;
}

if (tryContainerCommand("podman")) {
LOG.info("Podman is available");
return true;
}

LOG.warn("Neither Docker nor Podman is available");
return false;
}

private static boolean tryContainerCommand(String command) {
try {
Process process = new ProcessBuilder(command, "info")
.redirectOutput(ProcessBuilder.Redirect.DISCARD)
.redirectError(ProcessBuilder.Redirect.DISCARD)
.start();

boolean finished = process.waitFor(5, TimeUnit.SECONDS);
if (!finished) {
LOG.debug("Timeout waiting for '{} info' command, destroying process", command);
process.destroyForcibly();
return false;
}

int exitCode = process.exitValue();
return exitCode == 0;
} catch (IOException e) {
LOG.debug("Failed to execute '{} info' command", command, e);
return false;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOG.debug("Interrupted while waiting for '{} info' command", command, e);
return false;
}
}
}
Loading
Loading