Skip to content
Open
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
42 changes: 42 additions & 0 deletions .github/workflows/itk-nightly.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Nightly ITK

on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:

permissions:
contents: write

jobs:
nightly:
name: Nightly ITK Run
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Set up JDK 17
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'temurin'
cache: maven

- name: Run Nightly ITK Tests
run: bash run_itk.sh
working-directory: itk
env:
A2A_ITK_REVISION: main
ITK_NIGHTLY_RUN: "True"

- name: Upload Results to Rolling Release
uses: softprops/action-gh-release@v3
with:
tag_name: "nightly-metrics"
prerelease: true
files: |
itk/itk_java.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44 changes: 44 additions & 0 deletions .github/workflows/itk.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: ITK

on:
push:
branches: [main]
pull_request:
paths:
- 'client/**'
- 'common/**'
- 'http-client/**'
- 'itk/**'
- 'jsonrpc-common/**'
- 'reference/**'
- 'server-common/**'
- 'spec/**'
- 'spec-grpc/**'
- 'transport/**'
- 'pom.xml'
- '.github/workflows/itk.yaml'

permissions:
contents: read

jobs:
itk:
name: ITK
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Set up JDK 17
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'temurin'
cache: maven

- name: Run ITK Tests
run: bash run_itk.sh
working-directory: itk
env:
A2A_ITK_REVISION: main
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class ExtrasBomVerifier extends DynamicBomVerifier {
private static final Set<String> EXTRAS_EXCLUSIONS = Set.of(
"boms/", // BOM test modules themselves
"examples/", // Example applications
"itk/", // Integration Test Kit agent
"tck/", // TCK test suite
"tests/", // Integration tests
"test-utils-docker/", // Test utilities for Docker-based tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class ReferenceBomVerifier extends DynamicBomVerifier {
private static final Set<String> REFERENCE_EXCLUSIONS = Set.of(
"boms/", // BOM test modules themselves
"examples/", // Example applications
"itk/", // Integration Test Kit agent
"tck/", // TCK test suite
"tests/", // Integration tests
"test-utils-docker/", // Test utilities for Docker-based tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class SdkBomVerifier extends DynamicBomVerifier {
private static final Set<String> SDK_EXCLUSIONS = Set.of(
"boms/", // BOM test modules themselves
"examples/", // Example applications
"itk/", // Integration Test Kit agent
"tck/", // TCK test suite
"compat-0.3/tck/", // Compat 0.3 TCK (not yet enabled)
"compat-0.3/reference/", // Compat 0.3 reference implementations (in reference BOM)
Expand Down
4 changes: 4 additions & 0 deletions itk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
a2a-itk/
logs/
raw_results.json
itk_java.json
101 changes: 101 additions & 0 deletions itk/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.a2aproject.sdk</groupId>
<artifactId>a2a-java-sdk-parent</artifactId>
<version>1.0.0.CR2-SNAPSHOT</version>
</parent>

<artifactId>a2a-java-sdk-itk</artifactId>

<name>Java SDK A2A ITK Agent</name>
<description>Integration Test Kit agent for cross-SDK interoperability testing</description>

<properties>
<protobuf-java.version>4.33.2</protobuf-java.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-bom</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.a2aproject.sdk</groupId>
<artifactId>a2a-java-sdk-client</artifactId>
</dependency>
<dependency>
<groupId>org.a2aproject.sdk</groupId>
<artifactId>a2a-java-sdk-reference-jsonrpc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.a2aproject.sdk</groupId>
<artifactId>a2a-java-sdk-reference-grpc</artifactId>
</dependency>
<dependency>
<groupId>org.a2aproject.sdk</groupId>
<artifactId>a2a-java-sdk-reference-rest</artifactId>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf-java.version}</version>
</dependency>
<dependency>
<groupId>org.a2aproject.sdk</groupId>
<artifactId>a2a-java-sdk-client-transport-grpc</artifactId>
</dependency>
<dependency>
<groupId>org.a2aproject.sdk</groupId>
<artifactId>a2a-java-sdk-client-transport-rest</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
<configuration>
<jvmArgs>--add-opens=java.base/java.lang=ALL-UNNAMED</jvmArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
157 changes: 157 additions & 0 deletions itk/run_itk.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/bin/bash
set -ex

# Set default log level
export ITK_LOG_LEVEL="${ITK_LOG_LEVEL:-INFO}"

# Detect container runtime (docker or podman)
if command -v docker &> /dev/null; then
CONTAINER_RT=docker
elif command -v podman &> /dev/null; then
CONTAINER_RT=podman
else
echo "Error: neither docker nor podman found"
exit 1
fi

# Initialize default exit code
RESULT=1

# Cleanup function to be called on exit
cleanup() {
set +x
echo "Cleaning up artifacts..."
$CONTAINER_RT stop itk-service > /dev/null 2>&1 || true
$CONTAINER_RT rm itk-service > /dev/null 2>&1 || true
$CONTAINER_RT rmi itk_service > /dev/null 2>&1 || true
rm -rf a2a-itk > /dev/null 2>&1 || true
echo "Done. Final exit code: $RESULT"
}
Comment on lines +1 to +29
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the script fails while inside the a2a-itk directory, the cleanup trap will execute rm -rf a2a-itk from within that directory. Deleting the current working directory can fail or cause shell issues on some systems. It is safer to save the original directory at the start and cd back to it during cleanup.

#!/bin/bash
set -ex

# Save original directory to ensure cleanup runs from a safe location
ORIGINAL_DIR=$(pwd)

# Set default log level
export ITK_LOG_LEVEL="${ITK_LOG_LEVEL:-INFO}"

# Initialize default exit code
RESULT=1

# Cleanup function to be called on exit
cleanup() {
  set +x
  echo "Cleaning up artifacts..."
  cd "$ORIGINAL_DIR" > /dev/null 2>&1 || true
  docker stop itk-service > /dev/null 2>&1 || true
  docker rm itk-service > /dev/null 2>&1 || true
  docker rmi itk_service > /dev/null 2>&1 || true
  rm -rf a2a-itk > /dev/null 2>&1 || true
  echo "Done. Final exit code: $RESULT"
}


# Register cleanup function to run on script exit
trap cleanup EXIT

# 1. Pull a2a-itk and checkout revision
: "${A2A_ITK_REVISION:?A2A_ITK_REVISION environment variable must be set}"

if [ ! -d "a2a-itk" ]; then
git clone https://github.com/a2aproject/a2a-itk.git a2a-itk
fi
cd a2a-itk
git fetch origin
git checkout "$A2A_ITK_REVISION"

# Only pull if it's a branch (not a detached HEAD)
if git symbolic-ref -q HEAD > /dev/null; then
git pull origin "$A2A_ITK_REVISION"
fi
cd ..

# 2. Copy latest instruction.proto from a2a-itk
cp a2a-itk/protos/instruction.proto src/main/proto/instruction.proto

# 3. Build itk_service container image from root of a2a-itk
CONTAINER_BUILD_ARGS=""
if [ "$CONTAINER_RT" = "podman" ]; then
CONTAINER_BUILD_ARGS="--format docker"
fi
$CONTAINER_RT build $CONTAINER_BUILD_ARGS -t itk_service a2a-itk

# 4. Start container service with a single mount: the a2a-java repo
A2A_JAVA_ROOT=$(cd .. && pwd)

# Stop existing container if any
$CONTAINER_RT rm -f itk-service || true

# Create logs directory if debug
if [ "${ITK_LOG_LEVEL^^}" = "DEBUG" ]; then
mkdir -p logs
fi

DOCKER_MOUNT_LOGS=""
if [ "${ITK_LOG_LEVEL^^}" = "DEBUG" ]; then
DOCKER_MOUNT_LOGS="-v $(pwd)/logs:/app/logs"
fi

$CONTAINER_RT run -d --name itk-service \
-v "$A2A_JAVA_ROOT:/app/agents/repo" \
$DOCKER_MOUNT_LOGS \
-e ITK_LOG_LEVEL="$ITK_LOG_LEVEL" \
-p 8000:8000 \
itk_service

# 5. Verify service is up and send post request
MAX_RETRIES=30
echo "Waiting for ITK service to start on 127.0.0.1:8000..."
set +e
for i in $(seq 1 $MAX_RETRIES); do
if curl -s http://127.0.0.1:8000/ > /dev/null; then
echo "Service is up!"
break
fi
echo "Still waiting... ($i/$MAX_RETRIES)"
sleep 2
done

# If we reached the end of the loop without success
if ! curl -s http://127.0.0.1:8000/ > /dev/null; then
echo "Error: ITK service failed to start on port 8000"
$CONTAINER_RT logs itk-service
exit 1
fi

# Workaround: java_v10 agent targets Java 21 but the ITK image has JDK 17.
# Patch until https://github.com/a2aproject/a2a-itk/pull/XX merges.
$CONTAINER_RT exec itk-service sed -i 's|<maven.compiler.release>21</maven.compiler.release>|<maven.compiler.release>17</maven.compiler.release>|' /app/agents/java/v10/pom.xml 2>/dev/null || true

SCENARIO_FILE="scenarios.json"
if [ "${ITK_NIGHTLY_RUN^^}" = "TRUE" ]; then
SCENARIO_FILE="scenarios_full.json"
fi

echo "ITK Service is up! Sending compatibility test request using $SCENARIO_FILE..."
RESPONSE=$(curl -s -X POST http://127.0.0.1:8000/run \
-H "Content-Type: application/json" \
-d "@$SCENARIO_FILE")

if [ "${ITK_NIGHTLY_RUN^^}" = "TRUE" ]; then
echo "Nightly run detected. Saving raw results and running process_results.py..."
echo "$RESPONSE" > raw_results.json
python3 a2a-itk/scripts/process_results.py \
--history_output_file itk_java.json \
--history_url https://github.com/a2aproject/a2a-java/releases/download/nightly-metrics/itk_java.json
RESULT=$?
else
echo "--------------------------------------------------------"
echo "ITK TEST RESULTS:"
echo "--------------------------------------------------------"
echo "$RESPONSE" | python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
all_passed = data.get('all_passed', False)
results = data.get('results', {})
for test, passed in results.items():
status = 'PASSED' if passed else 'FAILED'
print(f'{test}: {status}')
print('--------------------------------------------------------')
print(f'OVERALL STATUS: {\"PASSED\" if all_passed else \"FAILED\"}')
if not all_passed:
sys.exit(1)
except Exception as e:
print(f'Error parsing results: {e}')
print(f'Raw response: {data if \"data\" in locals() else \"no data\"}')
sys.exit(1)
"
Comment on lines +128 to +145
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If json.load(sys.stdin) fails due to an invalid JSON response, the except block tries to print data, which is undefined, resulting in a secondary exception and hiding the actual raw response. Reading the raw stdin first allows you to print the exact invalid response for easier debugging.

Suggested change
echo "$RESPONSE" | python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
all_passed = data.get('all_passed', False)
results = data.get('results', {})
for test, passed in results.items():
status = 'PASSED' if passed else 'FAILED'
print(f'{test}: {status}')
print('--------------------------------------------------------')
print(f'OVERALL STATUS: {\"PASSED\" if all_passed else \"FAILED\"}')
if not all_passed:
sys.exit(1)
except Exception as e:
print(f'Error parsing results: {e}')
print(f'Raw response: {data if \"data\" in locals() else \"no data\"}')
sys.exit(1)
"
echo "$RESPONSE" | python3 -c "
import sys, json
raw = sys.stdin.read()
try:
data = json.loads(raw)
all_passed = data.get('all_passed', False)
results = data.get('results', {})
for test, passed in results.items():
status = 'PASSED' if passed else 'FAILED'
print(f'{test}: {status}')
print('--------------------------------------------------------')
print(f'OVERALL STATUS: {\\\"PASSED\\\" if all_passed else \\\"FAILED\\\"}')
if not all_passed:
sys.exit(1)
except Exception as e:
print(f'Error parsing results: {e}')
print(f'Raw response: {raw}')
sys.exit(1)
"

RESULT=$?
fi
set -e

if [ $RESULT -ne 0 ]; then
echo "Tests failed. Container logs:"
$CONTAINER_RT logs itk-service
fi
echo "--------------------------------------------------------"

# Final exit result will be captured by trap cleanup
exit $RESULT
Loading
Loading