From 9e4aeefd7e61885e6fe3683b7b6d7640567e7ea6 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 20 Oct 2026 19:39:06 +0200 Subject: [PATCH] test(jul): Move the JUL sample's system test into sentry-jul Adds SentryHandlerIntegrationTest, which configures the Sentry handler through a logging properties file read by LogManager, as the sample does, and checks what reaches MockSentryServer: the warning with its flag, the error with its exception and breadcrumbs, and the logs with their attributes. Its logger is outside io.sentry, whose loggers the handler drops, and the test restores the LogManager configuration the other tests read. The sample has no system tests left, so it comes off the system-test runner and the backend workflow, and drops the Kotlin plugin it only needed for the test. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/system-tests-backend.yml | 3 - sentry-jul/build.gradle.kts | 1 + .../jul/SentryHandlerIntegrationTest.kt | 69 +++++++++++++++ ...sentry-handler-integration-test.properties | 7 ++ .../sentry-samples-jul/build.gradle.kts | 36 -------- .../src/test/kotlin/io/sentry/DummyTest.kt | 12 --- .../ConsoleApplicationSystemTest.kt | 83 ------------------- test/system-test-runner.py | 1 - 8 files changed, 77 insertions(+), 135 deletions(-) create mode 100644 sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerIntegrationTest.kt create mode 100644 sentry-jul/src/test/resources/sentry-handler-integration-test.properties delete mode 100644 sentry-samples/sentry-samples-jul/src/test/kotlin/io/sentry/DummyTest.kt delete mode 100644 sentry-samples/sentry-samples-jul/src/test/kotlin/io/sentry/systemtest/ConsoleApplicationSystemTest.kt diff --git a/.github/workflows/system-tests-backend.yml b/.github/workflows/system-tests-backend.yml index eb3721139d1..d221dd165ea 100644 --- a/.github/workflows/system-tests-backend.yml +++ b/.github/workflows/system-tests-backend.yml @@ -34,9 +34,6 @@ jobs: - sample: "sentry-samples-logback" agent: "false" agent-auto-init: "true" - - sample: "sentry-samples-jul" - agent: "false" - agent-auto-init: "true" - sample: "sentry-samples-spring-boot-4-otlp" agent: "false" agent-auto-init: "true" diff --git a/sentry-jul/build.gradle.kts b/sentry-jul/build.gradle.kts index 130b46ad5ed..451cbf1991b 100644 --- a/sentry-jul/build.gradle.kts +++ b/sentry-jul/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { // tests testImplementation(projects.sentryTestSupport) testImplementation(kotlin(Config.kotlinStdLib)) + testImplementation(libs.google.truth) testImplementation(libs.kotlin.test.junit) testImplementation(libs.logback.classic) testImplementation(libs.mockito.kotlin) diff --git a/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerIntegrationTest.kt b/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerIntegrationTest.kt new file mode 100644 index 00000000000..95856f56aca --- /dev/null +++ b/sentry-jul/src/test/kotlin/io/sentry/jul/SentryHandlerIntegrationTest.kt @@ -0,0 +1,69 @@ +package io.sentry.jul + +import com.google.common.truth.Truth.assertThat +import io.sentry.Sentry +import io.sentry.SentryLevel +import io.sentry.test.MockSentryServer +import io.sentry.test.event +import io.sentry.test.hasFlag +import io.sentry.test.logs +import java.util.logging.Level +import java.util.logging.LogManager +import java.util.logging.Logger +import kotlin.test.AfterTest +import kotlin.test.Test + +class SentryHandlerIntegrationTest { + private val server = MockSentryServer() + + @AfterTest + fun teardown() { + // Back to the configuration the other tests read, which also closes this test's handler. + LogManager.getLogManager().readConfiguration() + Sentry.close() + server.close() + System.clearProperty("sentry.dsn") + } + + @Test + fun `handler configured from logging properties sends events, breadcrumbs and logs`() { + // The handler reads the DSN as external configuration, as an app's sentry.properties would + // set it. The module's test sentry.properties enables logs. + System.setProperty("sentry.dsn", server.dsn) + javaClass.getResourceAsStream("/sentry-handler-integration-test.properties")!!.use { + LogManager.getLogManager().readConfiguration(it) + } + // Outside io.sentry, whose loggers the handler drops so it doesn't capture the SDK's own logs. + val logger = Logger.getLogger("com.example.App") + + logger.config("Hello Sentry!") + Sentry.setAttribute("user.type", "admin") + Sentry.setAttribute("feature.version", 2) + Sentry.setAttribute("debug.enabled", true) + Sentry.addFeatureFlag("my-feature-flag", true) + logger.warning("important warning") + logger.log(Level.INFO, "User has made a purchase of product: %d", 445) + logger.log(Level.SEVERE, "Something went wrong", RuntimeException("Invalid productId=445")) + Sentry.flush(5000) + + val warning = server.awaitEnvelope { it.event()?.level == SentryLevel.WARNING }.event()!! + assertThat(warning.message!!.message).isEqualTo("important warning") + assertThat(warning.hasFlag("my-feature-flag", true)).isTrue() + + val error = server.awaitEnvelope { it.event()?.level == SentryLevel.ERROR }.event()!! + assertThat(error.message!!.message).isEqualTo("Something went wrong") + assertThat(error.exceptions!!.map { it.type to it.value }) + .contains("RuntimeException" to "Invalid productId=445") + assertThat(error.breadcrumbs!!.map { it.message to it.level }) + .containsAtLeast( + "Hello Sentry!" to SentryLevel.DEBUG, + "User has made a purchase of product: 445" to SentryLevel.INFO, + ) + + val logs = server.awaitEnvelope { it.logs() != null }.logs()!! + assertThat(logs.items.map { it.body }).contains("User has made a purchase of product: 445") + val failureLog = logs.items.single { it.body == "Something went wrong" } + assertThat(failureLog.attributes!!.mapValues { it.value.value }) + .containsAtLeast("user.type", "admin", "feature.version", 2, "debug.enabled", true) + } +} diff --git a/sentry-jul/src/test/resources/sentry-handler-integration-test.properties b/sentry-jul/src/test/resources/sentry-handler-integration-test.properties new file mode 100644 index 00000000000..f9e257cafcb --- /dev/null +++ b/sentry-jul/src/test/resources/sentry-handler-integration-test.properties @@ -0,0 +1,7 @@ +handlers=io.sentry.jul.SentryHandler +.level=FINEST +io.sentry.jul.SentryHandler.level=FINEST +io.sentry.jul.SentryHandler.minimumEventLevel=INFO +io.sentry.jul.SentryHandler.minimumBreadcrumbLevel=CONFIG +io.sentry.jul.SentryHandler.minimumLevel=INFO +io.sentry.jul.SentryHandler.printfStyle=true diff --git a/sentry-samples/sentry-samples-jul/build.gradle.kts b/sentry-samples/sentry-samples-jul/build.gradle.kts index 6fa4bbb8c8b..981455c455e 100644 --- a/sentry-samples/sentry-samples-jul/build.gradle.kts +++ b/sentry-samples/sentry-samples-jul/build.gradle.kts @@ -1,32 +1,15 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { java application - alias(libs.plugins.kotlin.jvm) alias(libs.plugins.gradle.versions) alias(libs.plugins.shadow) - id("io.sentry.systemtest") } application { mainClass.set("io.sentry.samples.jul.Main") } -tasks.withType().configureEach { - kotlin { - compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict") - } -} - dependencies { implementation(projects.sentryJul) implementation(libs.slf4j.jdk14) - - testImplementation(kotlin(Config.kotlinStdLib)) - testImplementation(projects.sentry) - testImplementation(projects.sentrySystemTestSupport) - testImplementation(libs.kotlin.test.junit) - testImplementation(libs.slf4j.api) - testImplementation(libs.slf4j.jdk14) } // Configure the Shadow JAR (executable JAR with all dependencies) @@ -44,22 +27,3 @@ tasks.jar { // Fix the startScripts task dependency tasks.startScripts { dependsOn(tasks.shadowJar) } - -tasks.register("systemTest").configure { - group = "verification" - description = "Runs the System tests" - - val test = project.extensions.getByType()["test"] - testClassesDirs = test.output.classesDirs - classpath = test.runtimeClasspath - - maxParallelForks = 1 - - 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-jul/src/test/kotlin/io/sentry/DummyTest.kt b/sentry-samples/sentry-samples-jul/src/test/kotlin/io/sentry/DummyTest.kt deleted file mode 100644 index 6f762b7e453..00000000000 --- a/sentry-samples/sentry-samples-jul/src/test/kotlin/io/sentry/DummyTest.kt +++ /dev/null @@ -1,12 +0,0 @@ -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-jul/src/test/kotlin/io/sentry/systemtest/ConsoleApplicationSystemTest.kt b/sentry-samples/sentry-samples-jul/src/test/kotlin/io/sentry/systemtest/ConsoleApplicationSystemTest.kt deleted file mode 100644 index 9750dcd728f..00000000000 --- a/sentry-samples/sentry-samples-jul/src/test/kotlin/io/sentry/systemtest/ConsoleApplicationSystemTest.kt +++ /dev/null @@ -1,83 +0,0 @@ -package io.sentry.systemtest - -import io.sentry.SentryLevel -import io.sentry.systemtest.util.TestHelper -import java.util.concurrent.TimeUnit -import org.junit.Assert -import org.junit.Before -import org.junit.Test - -class ConsoleApplicationSystemTest { - lateinit var testHelper: TestHelper - - @Before - fun setup() { - testHelper = TestHelper("http://localhost:8000") - testHelper.reset() - } - - @Test - fun `jul application sends expected events when run as JAR`() { - val jarFile = testHelper.findJar("sentry-samples-jul") - val process = - testHelper.launch( - jarFile, - mapOf( - "SENTRY_DSN" to testHelper.dsn, - "SENTRY_TRACES_SAMPLE_RATE" to "1.0", - "SENTRY_ENABLE_PRETTY_SERIALIZATION_OUTPUT" to "false", - "SENTRY_DEBUG" to "true", - ), - ) - - process.waitFor(30, TimeUnit.SECONDS) - Assert.assertEquals(0, process.exitValue()) - - // Verify that we received the expected events - verifyExpectedEvents() - } - - private fun verifyExpectedEvents() { - // Verify we received the RuntimeException - testHelper.ensureErrorReceived { event -> - event.exceptions?.any { ex -> - ex.type == "RuntimeException" && ex.value == "Invalid productId=445" - } == true && event.message?.message == "Something went wrong" && event.level?.name == "ERROR" - } - - testHelper.ensureErrorReceived { event -> - event.breadcrumbs?.firstOrNull { - it.message == "Hello Sentry!" && it.level == SentryLevel.DEBUG - } != null - } - - testHelper.ensureErrorReceived { event -> - event.breadcrumbs?.firstOrNull { - it.message == "User has made a purchase of product: 445" && it.level == SentryLevel.INFO - } != null - } - - testHelper.ensureErrorReceived { event -> - event.message?.message == "important warning" && - testHelper.doesEventHaveFlag(event, "my-feature-flag", true) - } - - testHelper.ensureLogsReceived { logs, _ -> - testHelper.doesContainLogWithBody(logs, "User has made a purchase of product: 445") && - testHelper.doesContainLogWithBody(logs, "Something went wrong") && - testHelper.doesLogWithBodyHaveAttribute( - logs, - "Something went wrong", - "user.type", - "admin", - ) && - testHelper.doesLogWithBodyHaveAttribute( - logs, - "Something went wrong", - "feature.version", - 2, - ) && - testHelper.doesLogWithBodyHaveAttribute(logs, "Something went wrong", "debug.enabled", true) - } - } -} diff --git a/test/system-test-runner.py b/test/system-test-runner.py index 612d711543e..58821315853 100644 --- a/test/system-test-runner.py +++ b/test/system-test-runner.py @@ -832,7 +832,6 @@ def get_available_modules(self) -> List[ModuleConfig]: ModuleConfig("sentry-samples-console-otlp", "false", "true", "false"), ModuleConfig("sentry-samples-console-opentelemetry-noagent", "false", "true", "false"), ModuleConfig("sentry-samples-logback", "false", "true", "false"), - ModuleConfig("sentry-samples-jul", "false", "true", "false"), ] def _find_module_number(self, module_name: str, agent: str, auto_init: str) -> int: