diff --git a/.github/workflows/full-build.yml b/.github/workflows/full-build.yml
index 94c444638e..92aa0143af 100644
--- a/.github/workflows/full-build.yml
+++ b/.github/workflows/full-build.yml
@@ -22,13 +22,24 @@ jobs:
java-version: 25
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v5
with:
java-version: ${{ matrix.java-version }}
distribution: 'temurin'
cache: maven
+
+ # 1. Set up Go (cross-platform)
+ - name: Set up Go
+ uses: actions/setup-go@v6
+ with:
+ go-version: 'stable'
+
+ # 2. Install grpcurl (this places it in the $PATH automatically)
+ - name: Install grpcurl
+ run: go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
+
- name: Build
run: mvn -B install -P gradlePlugin --no-transfer-progress
env:
@@ -109,3 +120,48 @@ jobs:
for test in sorted(skipped_tests):
f.write(f"{test}\n")
'
+
+ - name: š Coverage Report
+ if: always() && matrix.os == 'ubuntu-latest' && matrix.java-version == '21'
+ run: |
+ python3 -c '
+ import csv, os
+
+ # Hardcoded path to the aggregated CSV
+ csv_file = "tests/target/site/jacoco-aggregate/jacoco.csv"
+ summary_file = os.environ.get("GITHUB_STEP_SUMMARY")
+
+ if not os.path.exists(csv_file):
+ with open(summary_file, "a", encoding="utf-8") as f:
+ f.write(f"\nā ļø **Coverage report not found at {csv_file}.**\n")
+ else:
+ instr_missed, instr_covered = 0, 0
+ branch_missed, branch_covered = 0, 0
+ line_missed, line_covered = 0, 0
+
+ with open(csv_file, mode="r", encoding="utf-8") as file:
+ reader = csv.DictReader(file)
+ for row in reader:
+ instr_missed += int(row["INSTRUCTION_MISSED"])
+ instr_covered += int(row["INSTRUCTION_COVERED"])
+ branch_missed += int(row["BRANCH_MISSED"])
+ branch_covered += int(row["BRANCH_COVERED"])
+ line_missed += int(row["LINE_MISSED"])
+ line_covered += int(row["LINE_COVERED"])
+
+ def calc(covered, missed):
+ total = covered + missed
+ return (covered / total * 100) if total > 0 else 0
+
+ instr_pct = calc(instr_covered, instr_missed)
+ line_pct = calc(line_covered, line_missed)
+ branch_pct = calc(branch_covered, branch_missed)
+
+ with open(summary_file, "a", encoding="utf-8") as f:
+ f.write("## š Code Coverage\n")
+ f.write("| Metric | Coverage |\n")
+ f.write("|--------|----------|\n")
+ f.write(f"| **Instruction (Overall)** | **{instr_pct:.2f}%** |\n")
+ f.write(f"| **Line** | **{line_pct:.2f}%** |\n")
+ f.write(f"| **Branch** | **{branch_pct:.2f}%** |\n\n")
+ '
diff --git a/.github/workflows/maven-central.yml b/.github/workflows/maven-central.yml
index ace9352c2c..8b7ed4a41e 100644
--- a/.github/workflows/maven-central.yml
+++ b/.github/workflows/maven-central.yml
@@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Set up JDK 21
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: 21
distribution: 'temurin'
diff --git a/.github/workflows/reproducibility.yml b/.github/workflows/reproducibility.yml
index 817e55b1c1..d2c85ed3a5 100644
--- a/.github/workflows/reproducibility.yml
+++ b/.github/workflows/reproducibility.yml
@@ -12,7 +12,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
with:
ref: main
diff --git a/modules/jooby-apt/pom.xml b/modules/jooby-apt/pom.xml
index a09034fcd0..20f4bff3e8 100644
--- a/modules/jooby-apt/pom.xml
+++ b/modules/jooby-apt/pom.xml
@@ -181,48 +181,14 @@
org.apache.maven.plugins
- maven-shade-plugin
-
-
- fat-jar
-
- shade
-
- package
-
- true
-
-
- *:*
-
- META-INF/LICENSE
- META-INF/LICENSE.txt
- META-INF/NOTICE
- META-INF/NOTICE.txt
- META-INF/DEPENDENCIES
-
- META-INF/MANIFEST.MF
-
- module-info.class
- META-INF/versions/*/module-info.class
-
- META-INF/*.SF
- META-INF/*.DSA
- META-INF/*.RSA
-
-
-
-
-
-
- io.jooby.apt
-
-
-
-
-
-
-
+ maven-jar-plugin
+
+
+
+ io.jooby.apt
+
+
+
diff --git a/modules/jooby-flyway/pom.xml b/modules/jooby-flyway/pom.xml
index b7eccb13f6..7766f492d2 100644
--- a/modules/jooby-flyway/pom.xml
+++ b/modules/jooby-flyway/pom.xml
@@ -18,7 +18,6 @@
${jooby.version}
-
org.flywaydb
flyway-core
diff --git a/modules/jooby-openapi/pom.xml b/modules/jooby-openapi/pom.xml
index 47ba2d6cc3..92c6af9875 100644
--- a/modules/jooby-openapi/pom.xml
+++ b/modules/jooby-openapi/pom.xml
@@ -33,6 +33,12 @@
jakarta.validation
jakarta.validation-api
+
+
+ javax.xml.bind
+ jaxb-api
+ 2.3.1
+
org.ow2.asm
diff --git a/pom.xml b/pom.xml
index 52f5476ffe..6b4a941dec 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1409,6 +1409,14 @@
true
@{argLine}
+
+ false
+ 3.0
+ false
+ true
+ true
+ true
+
diff --git a/tests/src/test/java/io/jooby/i3500/WidgetService.java b/tests/src/test/java/io/jooby/i3500/WidgetService.java
index abc9f6c164..4746f70490 100644
--- a/tests/src/test/java/io/jooby/i3500/WidgetService.java
+++ b/tests/src/test/java/io/jooby/i3500/WidgetService.java
@@ -18,7 +18,6 @@ public WidgetService() {
"/api/widgets1",
ctx -> {
Widget widget = ctx.body().to(Widget.class);
- System.out.println("Created " + widget);
return ctx.send(StatusCode.CREATED);
});
diff --git a/tests/src/test/java/io/jooby/junit/CleanMethodNameGenerator.java b/tests/src/test/java/io/jooby/junit/CleanMethodNameGenerator.java
new file mode 100644
index 0000000000..a505774367
--- /dev/null
+++ b/tests/src/test/java/io/jooby/junit/CleanMethodNameGenerator.java
@@ -0,0 +1,27 @@
+/*
+ * Jooby https://jooby.io
+ * Apache License Version 2.0 https://jooby.io/LICENSE.txt
+ * Copyright 2014 Edgar Espina
+ */
+package io.jooby.junit;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.DisplayNameGenerator;
+
+public class CleanMethodNameGenerator extends DisplayNameGenerator.Standard {
+ @Override
+ public String generateDisplayNameForMethod(
+ List> enclosingInstanceTypes, Class> testClass, Method testMethod) {
+ // remove (ServerTestRunner) from test name:
+ var args =
+ Stream.of(testMethod.getParameters())
+ .filter(param -> !param.getType().equals(ServerTestRunner.class))
+ .toList();
+ return args.isEmpty()
+ ? testMethod.getName()
+ : super.generateDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod);
+ }
+}
diff --git a/tests/src/test/java/io/jooby/junit/ServerExtensionImpl.java b/tests/src/test/java/io/jooby/junit/ServerExtensionImpl.java
index 6c5a28f628..6d156aec01 100644
--- a/tests/src/test/java/io/jooby/junit/ServerExtensionImpl.java
+++ b/tests/src/test/java/io/jooby/junit/ServerExtensionImpl.java
@@ -117,7 +117,7 @@ private TestTemplateInvocationContext invocationContext(ServerInfo serverInfo) {
return new TestTemplateInvocationContext() {
@Override
public String getDisplayName(int invocationIndex) {
- return serverInfo.description;
+ return isMavenBuild() ? "(" + serverInfo.description + ")" : serverInfo.description;
}
@Override
@@ -138,4 +138,8 @@ private static String displayName(Class server, ExecutionMode mode, int i, int t
}
return displayName.toString();
}
+
+ static boolean isMavenBuild() {
+ return !System.getProperty("surefire.real.class.path", "").isEmpty();
+ }
}
diff --git a/tests/src/test/java/io/jooby/junit/ServerTestRunner.java b/tests/src/test/java/io/jooby/junit/ServerTestRunner.java
index 90e6feaba8..256e2bd6e8 100644
--- a/tests/src/test/java/io/jooby/junit/ServerTestRunner.java
+++ b/tests/src/test/java/io/jooby/junit/ServerTestRunner.java
@@ -91,8 +91,7 @@ public void ready(SneakyThrows.Consumer2 onReady) {
System.setProperty("___server_name__", server.getName());
var app = provider.get();
// Reduce log from maven build:
- var mavenBuild = System.getProperty("surefire.real.class.path", "").length() > 0;
- if (mavenBuild) {
+ if (ServerExtensionImpl.isMavenBuild()) {
applogger = app.getClass().getName();
app.setStartupSummary(List.of(StartupSummary.NONE));
app.error(
diff --git a/tests/src/test/kotlin/i2465/Issue2465.kt b/tests/src/test/kotlin/i2465/Issue2465.kt
index 57d03361f5..af46042aff 100644
--- a/tests/src/test/kotlin/i2465/Issue2465.kt
+++ b/tests/src/test/kotlin/i2465/Issue2465.kt
@@ -35,13 +35,13 @@ class Issue2465 {
client.get("/2465") { rsp ->
Assertions.assertEquals("/2465", rsp.header("After"))
Assertions.assertEquals("false", rsp.header("Response-Started"))
- Assertions.assertEquals("/2465", rsp.body!!.string())
+ Assertions.assertEquals("/2465", rsp.body.string())
}
client.get("/fun/2465") { rsp ->
Assertions.assertEquals("/fun/2465", rsp.header("After"))
Assertions.assertEquals("false", rsp.header("Response-Started"))
- Assertions.assertEquals("/fun/2465", rsp.body!!.string())
+ Assertions.assertEquals("/fun/2465", rsp.body.string())
}
}
}
diff --git a/tests/src/test/kotlin/i2710/Issue2710.kt b/tests/src/test/kotlin/i2710/Issue2710.kt
index eb22624ad7..2d3fd86b3c 100644
--- a/tests/src/test/kotlin/i2710/Issue2710.kt
+++ b/tests/src/test/kotlin/i2710/Issue2710.kt
@@ -31,7 +31,7 @@ class Issue2710 {
}
}
.ready { client ->
- client.get("/2710") { rsp -> Assertions.assertEquals("OK", rsp.body!!.string()) }
+ client.get("/2710") { rsp -> Assertions.assertEquals("OK", rsp.body.string()) }
}
latch.await()
}
diff --git a/tests/src/test/kotlin/i3228/Issue3228.kt b/tests/src/test/kotlin/i3228/Issue3228.kt
index a3592c79ec..0ea6d86b9c 100644
--- a/tests/src/test/kotlin/i3228/Issue3228.kt
+++ b/tests/src/test/kotlin/i3228/Issue3228.kt
@@ -59,13 +59,13 @@ class Issue3228 {
}
.ready { client ->
client.get("/i3228/without-worker") { rsp ->
- Assertions.assertEquals("nonBlocking: true, coroutine: true", rsp.body!!.string())
+ Assertions.assertEquals("nonBlocking: true, coroutine: true", rsp.body.string())
}
client.get("/i3228/without-worker-no-coroutine") { rsp ->
- Assertions.assertEquals("nonBlocking: true, coroutine: true", rsp.body!!.string())
+ Assertions.assertEquals("nonBlocking: true, coroutine: true", rsp.body.string())
}
client.get("/i3228/with-worker") { rsp ->
- Assertions.assertEquals("nonBlocking: true, coroutine: true", rsp.body!!.string())
+ Assertions.assertEquals("nonBlocking: true, coroutine: true", rsp.body.string())
}
}
}
diff --git a/tests/src/test/kotlin/i3405/Issue3405.kt b/tests/src/test/kotlin/i3405/Issue3405.kt
index 9a4668d287..ae06d60ae0 100644
--- a/tests/src/test/kotlin/i3405/Issue3405.kt
+++ b/tests/src/test/kotlin/i3405/Issue3405.kt
@@ -25,7 +25,7 @@ class Issue3405 {
}
.ready { client ->
client.get("/i3405/normal-error") { rsp ->
- assertEquals("normal/global", rsp.body!!.string())
+ assertEquals("normal/global", rsp.body.string())
}
}
@@ -50,7 +50,7 @@ class Issue3405 {
}
.ready { client ->
client.get("/i3405/coroutine-error") { rsp ->
- assertEquals("coroutine/suspended", rsp.body!!.string())
+ assertEquals("coroutine/suspended", rsp.body.string())
}
}
@@ -73,8 +73,8 @@ class Issue3405 {
}
.ready { client ->
client.get("/i3405/coroutine-error") { rsp ->
- assertEquals("coroutine/suspended", rsp.body!!.string())
+ assertEquals("coroutine/suspended", rsp.body.string())
}
- client.get("/i3405/chain") { rsp -> assertEquals("conflict", rsp.body!!.string()) }
+ client.get("/i3405/chain") { rsp -> assertEquals("conflict", rsp.body.string()) }
}
}
diff --git a/tests/src/test/kotlin/i3476/Issue3476.kt b/tests/src/test/kotlin/i3476/Issue3476.kt
index 2779711e9b..f9015c6c27 100644
--- a/tests/src/test/kotlin/i3476/Issue3476.kt
+++ b/tests/src/test/kotlin/i3476/Issue3476.kt
@@ -21,5 +21,5 @@ class Issue3476 {
mvc(C3476_())
}
}
- .ready { client -> client.get("/3476") { rsp -> assertEquals("[]", rsp.body!!.string()) } }
+ .ready { client -> client.get("/3476") { rsp -> assertEquals("[]", rsp.body.string()) } }
}
diff --git a/tests/src/test/kotlin/i3477/Issue3477.kt b/tests/src/test/kotlin/i3477/Issue3477.kt
index 44c2f597a9..4904e9b44d 100644
--- a/tests/src/test/kotlin/i3477/Issue3477.kt
+++ b/tests/src/test/kotlin/i3477/Issue3477.kt
@@ -22,8 +22,6 @@ class Issue3477 {
}
}
.ready { client ->
- client.get("/3477") { rsp ->
- assertEquals("{\"Transactional\":false}", rsp.body!!.string())
- }
+ client.get("/3477") { rsp -> assertEquals("{\"Transactional\":false}", rsp.body.string()) }
}
}
diff --git a/tests/src/test/kotlin/io/jooby/FeaturedKotlinTest.kt b/tests/src/test/kotlin/io/jooby/FeaturedKotlinTest.kt
index b064ec3c1f..a264a97db3 100644
--- a/tests/src/test/kotlin/io/jooby/FeaturedKotlinTest.kt
+++ b/tests/src/test/kotlin/io/jooby/FeaturedKotlinTest.kt
@@ -27,7 +27,7 @@ class FeaturedKotlinTest {
runner
.define { app -> app.get("/") { "Hello World!" } }
.ready { client ->
- client.get("/") { rsp -> assertEquals("Hello World!", rsp.body!!.string()) }
+ client.get("/") { rsp -> assertEquals("Hello World!", rsp.body.string()) }
}
}
@@ -36,7 +36,7 @@ class FeaturedKotlinTest {
runner
.use { -> Kooby { get("/") { ctx.send("Hello World!") } } }
.ready { client ->
- client.get("/") { rsp -> assertEquals("Hello World!", rsp.body!!.string()) }
+ client.get("/") { rsp -> assertEquals("Hello World!", rsp.body.string()) }
}
}
@@ -44,9 +44,7 @@ class FeaturedKotlinTest {
fun coroutineNoSuspend(runner: ServerTestRunner) {
runner
.use { -> Kooby { coroutine { get("/") { ctx.getRequestPath() + "coroutine" } } } }
- .ready { client ->
- client.get("/") { rsp -> assertEquals("/coroutine", rsp.body!!.string()) }
- }
+ .ready { client -> client.get("/") { rsp -> assertEquals("/coroutine", rsp.body.string()) } }
}
@ServerTest
@@ -62,9 +60,7 @@ class FeaturedKotlinTest {
}
}
}
- .ready { client ->
- client.get("/") { rsp -> assertEquals("/coroutine", rsp.body!!.string()) }
- }
+ .ready { client -> client.get("/") { rsp -> assertEquals("/coroutine", rsp.body.string()) } }
}
@ServerTest
@@ -83,7 +79,7 @@ class FeaturedKotlinTest {
}
}
.ready { client ->
- client.get("/") { rsp -> assertEquals("1,2,3,4,5,6,7,8,9,10,", rsp.body!!.string()) }
+ client.get("/") { rsp -> assertEquals("1,2,3,4,5,6,7,8,9,10,", rsp.body.string()) }
}
}
@@ -92,16 +88,16 @@ class FeaturedKotlinTest {
runner
.define { app -> app.mvc(KotlinMvc_()) }
.ready { client ->
- client.get("/kotlin") { rsp -> assertEquals("Got it!", rsp.body!!.string()) }
+ client.get("/kotlin") { rsp -> assertEquals("Got it!", rsp.body.string()) }
- client.get("/kotlin/78") { rsp -> assertEquals("78", rsp.body!!.string()) }
+ client.get("/kotlin/78") { rsp -> assertEquals("78", rsp.body.string()) }
client.get("/kotlin/point?x=8&y=1") { rsp ->
- assertEquals("QueryPoint(x=8, y=1) : 8", rsp.body!!.string())
+ assertEquals("QueryPoint(x=8, y=1) : 8", rsp.body.string())
}
client.get("/kotlin/point") { rsp ->
- val body = rsp.body!!.string()
+ val body = rsp.body.string()
assertTrue(
body.contains(
"Cannot convert value: 'null', to: 'io.jooby.internal.mvc.QueryPoint'"
@@ -110,11 +106,11 @@ class FeaturedKotlinTest {
}
client.get("/kotlin/point?x=9") { rsp ->
- assertEquals("QueryPoint(x=9, y=null) : 9", rsp.body!!.string())
+ assertEquals("QueryPoint(x=9, y=null) : 9", rsp.body.string())
}
client.get("/kotlin/point?x=9&y=8") { rsp ->
- assertEquals("QueryPoint(x=9, y=8) : 9", rsp.body!!.string())
+ assertEquals("QueryPoint(x=9, y=8) : 9", rsp.body.string())
}
}
}
@@ -133,14 +129,14 @@ class FeaturedKotlinTest {
}
}
.ready { client ->
- client.get("/") { rsp -> assertEquals("Got it!", rsp.body!!.string()) }
+ client.get("/") { rsp -> assertEquals("Got it!", rsp.body.string()) }
- client.get("/delay") { rsp -> assertEquals("/delay", rsp.body!!.string()) }
+ client.get("/delay") { rsp -> assertEquals("/delay", rsp.body.string()) }
- client.get("/456") { rsp -> assertEquals("456", rsp.body!!.string()) }
+ client.get("/456") { rsp -> assertEquals("456", rsp.body.string()) }
client.get("/456x") { rsp ->
- assertEquals("Cannot convert value: 'id', to: 'int'", rsp.body!!.string())
+ assertEquals("Cannot convert value: 'id', to: 'int'", rsp.body.string())
}
}
}
@@ -174,7 +170,7 @@ class FeaturedKotlinTest {
}
.ready { client ->
client.get("/") { rsp ->
- val json = JSONObject(rsp.body!!.string())
+ val json = JSONObject(rsp.body.string())
assertNotEquals("<>", json.get("key"))
assertNotEquals("<>", json.get("thread"))
assertNotEquals(json.get("thread"), json.get("currentThread"))
diff --git a/tests/src/test/resources/junit-platform.properties b/tests/src/test/resources/junit-platform.properties
new file mode 100644
index 0000000000..bf35953eee
--- /dev/null
+++ b/tests/src/test/resources/junit-platform.properties
@@ -0,0 +1 @@
+junit.jupiter.displayname.generator.default=io.jooby.junit.CleanMethodNameGenerator