diff --git a/.github/workflows/system-tests-backend.yml b/.github/workflows/system-tests-backend.yml index 15b4460d9ab..36d2977875d 100644 --- a/.github/workflows/system-tests-backend.yml +++ b/.github/workflows/system-tests-backend.yml @@ -81,6 +81,9 @@ jobs: - sample: "sentry-samples-spring-jakarta" agent: "false" agent-auto-init: "true" + - sample: "sentry-samples-spring" + agent: "false" + agent-auto-init: "true" steps: - uses: actions/checkout@v4 with: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 16cc70ce6b4..49cd419b68f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -97,6 +97,7 @@ feign-gson = { module = "io.github.openfeign:feign-gson", version.ref = "feign" graphql-java17 = { module = "com.graphql-java:graphql-java", version = "17.3" } graphql-java22 = { module = "com.graphql-java:graphql-java", version = "22.1" } graphql-java24 = { module = "com.graphql-java:graphql-java", version = "24.0" } +jackson-bom = { module = "com.fasterxml.jackson:jackson-bom", version.ref = "jackson" } jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } jetbrains-annotations = { module = "org.jetbrains:annotations", version = "23.0.0" } @@ -176,6 +177,8 @@ springboot4-starter-actuator = { module = "org.springframework.boot:spring-boot- timber = { module = "com.jakewharton.timber:timber", version = "4.7.1" } # tomcat libraries +tomcat-catalina = { module = "org.apache.tomcat:tomcat-catalina", version = "9.0.108" } +tomcat-embed-jasper = { module = "org.apache.tomcat.embed:tomcat-embed-jasper", version = "9.0.108" } tomcat-catalina-jakarta = { module = "org.apache.tomcat:tomcat-catalina", version = "11.0.10" } tomcat-embed-jasper-jakarta = { module = "org.apache.tomcat.embed:tomcat-embed-jasper", version = "11.0.10" } diff --git a/sentry-samples/sentry-samples-spring/build.gradle.kts b/sentry-samples/sentry-samples-spring/build.gradle.kts index 986493af9bc..cb114149452 100644 --- a/sentry-samples/sentry-samples-spring/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring/build.gradle.kts @@ -1,65 +1,90 @@ -import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES plugins { - alias(libs.plugins.springboot2) apply false - alias(libs.plugins.spring.dependency.management) - alias(libs.plugins.kotlin.jvm) - alias(libs.plugins.kotlin.spring) - id("war") - alias(libs.plugins.gretty) + application + alias(libs.plugins.springboot2) apply false + alias(libs.plugins.spring.dependency.management) + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlin.spring) + id("war") + alias(libs.plugins.gretty) } +application { mainClass.set("io.sentry.samples.spring.Main") } + +// Ensure WAR is up to date before run task +tasks.named("run") { dependsOn(tasks.named("war")) } + group = "io.sentry.sample.spring" + version = "0.0.1-SNAPSHOT" java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } -spotless { - kotlinGradle { - // This file throws an unclear error - targetExclude("build.gradle.kts") - } -} - -repositories { - mavenCentral() -} +repositories { mavenCentral() } dependencyManagement { - imports { - mavenBom(BOM_COORDINATES) - } + imports { + mavenBom(BOM_COORDINATES) + mavenBom(libs.kotlin.bom.get().toString()) + mavenBom(libs.jackson.bom.get().toString()) + } } dependencies { - implementation(Config.Libs.springWeb) - implementation(Config.Libs.springAop) - implementation(Config.Libs.aspectj) - implementation(Config.Libs.springSecurityWeb) - implementation(Config.Libs.springSecurityConfig) - implementation(Config.Libs.kotlinReflect) - implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) - implementation(projects.sentrySpring) - implementation(projects.sentryLogback) - implementation(libs.logback.classic) - implementation(libs.servlet.api) - testImplementation(libs.springboot.starter.test) { - exclude(group = "org.junit.vintage", module = "junit-vintage-engine") - } -} + implementation(Config.Libs.springWeb) + implementation(Config.Libs.springAop) + implementation(Config.Libs.aspectj) + implementation(Config.Libs.springSecurityWeb) + implementation(Config.Libs.springSecurityConfig) + implementation(Config.Libs.kotlinReflect) + implementation(kotlin(Config.kotlinStdLib)) + implementation(projects.sentrySpring) + implementation(projects.sentryLogback) + implementation(libs.jackson.databind) + implementation(libs.logback.classic) + implementation(libs.servlet.api) -tasks.withType().configureEach { - useJUnitPlatform() + implementation(libs.tomcat.catalina) + implementation(libs.tomcat.embed.jasper) + + testImplementation(projects.sentrySystemTestSupport) + testImplementation(libs.kotlin.test.junit) + testImplementation(libs.springboot.starter.test) { + exclude(group = "org.junit.vintage", module = "junit-vintage-engine") + } } tasks.withType().configureEach { - kotlin { - compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict") - compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8 - } + kotlin { + compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict") + compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8 + } +} + +configure { test { java.srcDir("src/test/java") } } + +tasks.register("systemTest").configure { + group = "verification" + description = "Runs the System tests" + + outputs.upToDateWhen { false } + + maxParallelForks = 1 + + // Cap JVM args per test + minHeapSize = "128m" + maxHeapSize = "1g" + + filter { includeTestsMatching("io.sentry.systemtest*") } +} + +tasks.named("test").configure { + require(this is Test) + + filter { excludeTestsMatching("io.sentry.systemtest.*") } } diff --git a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/Main.java b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/Main.java new file mode 100644 index 00000000000..3f807949096 --- /dev/null +++ b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/Main.java @@ -0,0 +1,32 @@ +package io.sentry.samples.spring; + +import java.io.File; +import java.io.IOException; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.startup.Tomcat; + +public class Main { + + public static void main(String[] args) throws LifecycleException, IOException { + File webappsDirectory = new File("./tomcat.8080/webapps"); + if (!webappsDirectory.exists()) { + boolean didCreateDirectories = webappsDirectory.mkdirs(); + if (!didCreateDirectories) { + throw new RuntimeException( + "Failed to create directory required by Tomcat: " + webappsDirectory.getAbsolutePath()); + } + } + + String pathToWar = "./build/libs"; + String warName = "sentry-samples-spring-0.0.1-SNAPSHOT"; + File war = new File(pathToWar + "/" + warName + ".war"); + + Tomcat tomcat = new Tomcat(); + tomcat.setPort(8080); + tomcat.getConnector(); + + tomcat.addWebapp("/" + warName, war.getCanonicalPath()); + tomcat.start(); + tomcat.getServer().await(); + } +} diff --git a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/web/Person.java b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/web/Person.java index 20a81f03bb4..8976b32cc3a 100644 --- a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/web/Person.java +++ b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/web/Person.java @@ -1,10 +1,15 @@ package io.sentry.samples.spring.web; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + public class Person { private final String firstName; private final String lastName; - public Person(String firstName, String lastName) { + @JsonCreator + public Person( + @JsonProperty("firstName") String firstName, @JsonProperty("lastName") String lastName) { this.firstName = firstName; this.lastName = lastName; } diff --git a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/web/PersonController.java b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/web/PersonController.java index b30fb577280..37da24d5812 100644 --- a/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/web/PersonController.java +++ b/sentry-samples/sentry-samples-spring/src/main/java/io/sentry/samples/spring/web/PersonController.java @@ -22,7 +22,7 @@ public PersonController(PersonService personService) { } @GetMapping("{id}") - Person person(@PathVariable Long id) { + Person person(@PathVariable("id") Long id) { Sentry.logger().warn("warn Sentry logging"); Sentry.logger().error("error Sentry logging"); Sentry.logger().info("hello %s %s", "there", "world!"); diff --git a/sentry-samples/sentry-samples-spring/src/test/kotlin/io/sentry/DummyTest.kt b/sentry-samples/sentry-samples-spring/src/test/kotlin/io/sentry/DummyTest.kt new file mode 100644 index 00000000000..6f762b7e453 --- /dev/null +++ b/sentry-samples/sentry-samples-spring/src/test/kotlin/io/sentry/DummyTest.kt @@ -0,0 +1,12 @@ +package io.sentry + +import kotlin.test.Test +import kotlin.test.assertTrue + +class DummyTest { + @Test + fun `the only test`() { + // only needed to have more than 0 tests and not fail the build + assertTrue(true) + } +} diff --git a/sentry-samples/sentry-samples-spring/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt b/sentry-samples/sentry-samples-spring/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt new file mode 100644 index 00000000000..97b826e10d0 --- /dev/null +++ b/sentry-samples/sentry-samples-spring/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt @@ -0,0 +1,46 @@ +package io.sentry.systemtest + +import io.sentry.systemtest.util.TestHelper +import kotlin.test.Test +import kotlin.test.assertEquals +import org.junit.Before + +class PersonSystemTest { + lateinit var testHelper: TestHelper + + @Before + fun setup() { + testHelper = TestHelper("http://localhost:8080/sentry-samples-spring-0.0.1-SNAPSHOT") + testHelper.reset() + } + + @Test + fun `get person fails`() { + val restClient = testHelper.restClient + restClient.getPerson(11L) + assertEquals(500, restClient.lastKnownStatusCode) + + testHelper.ensureTransactionReceived { transaction, envelopeHeader -> + testHelper.doesTransactionHaveOp(transaction, "http.server") + } + + Thread.sleep(10000) + + testHelper.ensureLogsReceived { logs, envelopeHeader -> + testHelper.doesContainLogWithBody(logs, "warn Sentry logging") && + testHelper.doesContainLogWithBody(logs, "error Sentry logging") && + testHelper.doesContainLogWithBody(logs, "hello there world!") + } + } + + @Test + fun `create person works`() { + val restClient = testHelper.restClient + val person = Person("firstA", "lastB") + val returnedPerson = restClient.createPerson(person) + assertEquals(200, restClient.lastKnownStatusCode) + + assertEquals(person.firstName, returnedPerson!!.firstName) + assertEquals(person.lastName, returnedPerson!!.lastName) + } +} diff --git a/sentry-system-test-support/build.gradle.kts b/sentry-system-test-support/build.gradle.kts index 2c015326c92..91710e1ea80 100644 --- a/sentry-system-test-support/build.gradle.kts +++ b/sentry-system-test-support/build.gradle.kts @@ -10,11 +10,11 @@ plugins { configure { sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_1_8 } tasks.withType().configureEach { - compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8 compilerOptions.languageVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9 compilerOptions.apiVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9 } @@ -26,10 +26,8 @@ dependencies { compileOnly(libs.jetbrains.annotations) compileOnly(libs.nopen.annotations) - compileOnly(libs.springboot3.starter.test) { - exclude(group = "org.junit.vintage", module = "junit-vintage-engine") - } - compileOnly(libs.springboot3.starter.web) + + compileOnly(libs.springboot.starter.web) implementation(libs.jackson.databind) implementation(libs.jackson.kotlin) diff --git a/test/system-test-runner.py b/test/system-test-runner.py index 829de6adccc..c25c79dcb9d 100644 --- a/test/system-test-runner.py +++ b/test/system-test-runner.py @@ -712,6 +712,7 @@ def run_manual_test_mode(self, sample_module: str, java_agent: str, def get_available_modules(self) -> List[ModuleConfig]: """Get list of all available test modules.""" return [ + ModuleConfig("sentry-samples-spring", "false", "true", "false"), ModuleConfig("sentry-samples-spring-jakarta", "false", "true", "false"), ModuleConfig("sentry-samples-spring-boot", "false", "true", "false"), ModuleConfig("sentry-samples-spring-boot-opentelemetry-noagent", "false", "true", "false"),