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
33 changes: 33 additions & 0 deletions .github/workflows/java-agent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Java Agent

on:
pull_request:
branches: [main]
push:
branches: [main]

jobs:
verify:
name: JDK ${{ matrix.java-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
java-version: ["8", "17", "21"]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ matrix.java-version }}
cache: maven

- name: Build
run: mvn -B -DskipTests clean verify

- name: Smoke test Java agent
run: ./scripts/smoke-javaagent.sh
3 changes: 3 additions & 0 deletions .woodpecker/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ steps:
image: maven:3.9-eclipse-temurin-8
commands:
- mvn -B -DskipTests clean verify
- ./scripts/smoke-javaagent.sh

build-jdk-17:
image: maven:3.9-eclipse-temurin-17
commands:
- mvn -B -DskipTests clean verify
- ./scripts/smoke-javaagent.sh

build-jdk-21:
image: maven:3.9-eclipse-temurin-21
commands:
- mvn -B -DskipTests clean verify
- ./scripts/smoke-javaagent.sh
91 changes: 41 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,78 +23,69 @@ Coverage is collected at per-testcase granularity, not process granularity.

## How to Use

### 1. Add the SDK
### 1. Download the Keploy Java Agent

Add `keploy-sdk` to your application:
Download the `keploy-sdk` jar and keep it outside your application dependencies. The jar is a Java agent and should be attached only when you run Keploy dynamic deduplication.

```xml
<dependency>
<groupId>io.keploy</groupId>
<artifactId>keploy-sdk</artifactId>
<version>2.0.0</version>
</dependency>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<id>copy-keploy-java-agent</id>
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>io.keploy</groupId>
<artifactId>keploy-sdk</artifactId>
<version>2.0.0</version>
<outputDirectory>${project.build.directory}</outputDirectory>
<destFileName>keploy-sdk.jar</destFileName>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
```

### 2. Activate the Agent
The SDK no longer has to be added to `dependencies`, and application code should not import `io.keploy.*` classes for dynamic deduplication.

For Spring Boot, import the middleware in your application:

```java
import io.keploy.servlet.KeployMiddleware;
import org.springframework.context.annotation.Import;

@Import(KeployMiddleware.class)
public class Application {
}
```

For servlet-based applications, register the filter early in `web.xml`:

```xml
<filter>
<filter-name>middleware</filter-name>
<filter-class>io.keploy.servlet.KeployMiddleware</filter-class>
</filter>
<filter-mapping>
<filter-name>middleware</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
```

The middleware starts the Java dedup control server automatically.

For Jakarta Servlet stacks, non-servlet frameworks, or any application where the `javax.servlet` filter is not available, start the agent directly during application startup:

```java
import io.keploy.dedup.KeployDedupAgent;

KeployDedupAgent.start();
```

### 3. Run the App with the JaCoCo Java Agent
### 2. Run the App with the Keploy and JaCoCo Java Agents

The dedup agent reads coverage in-process via JaCoCo's runtime API (`org.jacoco.agent.rt.RT.getAgent()`), so attaching the JaCoCo Java agent is the only runtime requirement in the common cases below:

- Maven/Gradle dev runs where application classes are under `target/classes` or `build/classes/java/main`
- packaged `java -jar` runs where the application classes live inside the executable jar

```bash
java -javaagent:/path/to/jacocoagent.jar -jar your-app.jar
java \
-javaagent:/path/to/keploy-sdk.jar \
-javaagent:/path/to/jacocoagent.jar \
-jar your-app.jar
```

If the in-process API is unavailable (for example because the JaCoCo agent is loaded into an isolated classloader), the SDK transparently falls back to JaCoCo's TCP server mode. To use the fallback explicitly, start JaCoCo in `tcpserver` mode and set `KEPLOY_JACOCO_HOST` / `KEPLOY_JACOCO_PORT`:

```bash
java -javaagent:/path/to/jacocoagent.jar=address=127.0.0.1,port=36320,output=tcpserver \
java \
-javaagent:/path/to/keploy-sdk.jar \
-javaagent:/path/to/jacocoagent.jar=address=127.0.0.1,port=36320,output=tcpserver \
-jar your-app.jar
```

### 4. Replay with Keploy Enterprise
### 3. Replay with Keploy Enterprise

Run replay with dynamic dedup enabled:

```bash
keploy test -c "java -javaagent:/path/to/jacocoagent.jar -jar your-app.jar" \
keploy test -c "java -javaagent:/path/to/keploy-sdk.jar -javaagent:/path/to/jacocoagent.jar -jar your-app.jar" \
--dedup \
--language java
```
Expand Down Expand Up @@ -128,8 +119,8 @@ Without a shared `/tmp`, dedup will not work inside containers because Enterpris

- `KEPLOY_JACOCO_HOST`: JaCoCo TCP host used when the in-process runtime API is unavailable. Default: `127.0.0.1`
- `KEPLOY_JACOCO_PORT`: JaCoCo TCP port used when the in-process runtime API is unavailable. Default: `36320`
- `KEPLOY_JAVA_CLASS_DIRS`: optional comma-separated class or jar locations to analyze for executed lines when your build output lives outside the standard locations
- `KEPLOY_JAVA_CLASSPATH_FALLBACK`: scans the full classpath if standard class roots and the executable jar do not provide application classes. Default: `false`
- `KEPLOY_JAVA_CLASS_DIRS`: optional comma-separated class, jar, war, ear, or zip locations to analyze for executed lines when your build output lives outside the standard locations
- `KEPLOY_JAVA_CLASSPATH_FALLBACK`: scans the full classpath if standard class roots and the executable archive do not provide application classes. Default: `true`
- `KEPLOY_JAVA_DEDUP_DISABLED`: disables the Java dedup agent when set to `true`, `1`, or `yes`

## Sample
Expand All @@ -138,4 +129,4 @@ For a working reference, see the Java dedup sample in `keploy/samples-java`:

- `samples-java/java-dedup`

That sample is used in CI to validate Java dynamic dedup for JDK 8, 17, and 21 across native, Docker, and restricted Docker runs.
That sample is used in CI to validate Java dynamic dedup for JDK 8, 17, and 21 across native, classpath, Docker, distroless, and restricted Docker runs.
46 changes: 44 additions & 2 deletions keploy-sdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
<parent>
<artifactId>java-sdk</artifactId>
<groupId>io.keploy</groupId>
<version>1.0.0-SNAPSHOT</version>
<version>2.0.0</version>
</parent>

<artifactId>keploy-sdk</artifactId>
<version>0.0.1-SNAPSHOT</version>
<version>2.0.0</version>
<name>Keploy Java Coverage Agent</name>
<description>Java dynamic dedup coverage agent for Keploy Enterprise</description>
<url>https://github.com/keploy/java-sdk</url>
Expand Down Expand Up @@ -78,6 +78,48 @@

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<id>shade-java-agent</id>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Premain-Class>io.keploy.dedup.KeployDedupAgent</Premain-Class>
<Agent-Class>io.keploy.dedup.KeployDedupAgent</Agent-Class>
<Can-Redefine-Classes>false</Can-Redefine-Classes>
<Can-Retransform-Classes>false</Can-Retransform-Classes>
<Implementation-Title>${project.name}</Implementation-Title>
<Implementation-Version>${project.version}</Implementation-Version>
<Automatic-Module-Name>io.keploy.sdk</Automatic-Module-Name>
</manifestEntries>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
<exclude>module-info.class</exclude>
<exclude>META-INF/versions/*/module-info.class</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
Expand Down
Loading
Loading