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