Skip to content

Commit d5fd3b0

Browse files
committed
Implement E2E tests for OTel based console sample
1 parent 195937c commit d5fd3b0

File tree

6 files changed

+357
-229
lines changed

6 files changed

+357
-229
lines changed
Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,83 @@
1+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2+
13
plugins {
24
java
35
application
6+
kotlin("jvm")
47
alias(libs.plugins.gradle.versions)
8+
id("com.github.johnrengelman.shadow") version "8.1.1"
59
}
610

711
application { mainClass.set("io.sentry.samples.console.Main") }
812

13+
java.sourceCompatibility = JavaVersion.VERSION_17
14+
15+
java.targetCompatibility = JavaVersion.VERSION_17
16+
17+
repositories { mavenCentral() }
18+
919
configure<JavaPluginExtension> {
10-
sourceCompatibility = JavaVersion.VERSION_1_8
11-
targetCompatibility = JavaVersion.VERSION_1_8
20+
sourceCompatibility = JavaVersion.VERSION_17
21+
targetCompatibility = JavaVersion.VERSION_17
22+
}
23+
24+
tasks.withType<KotlinCompile>().configureEach {
25+
kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString()
26+
}
27+
28+
tasks.withType<KotlinCompile>().configureEach {
29+
kotlinOptions {
30+
freeCompilerArgs = listOf("-Xjsr305=strict")
31+
jvmTarget = JavaVersion.VERSION_17.toString()
32+
}
33+
}
34+
35+
dependencies {
36+
implementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentless)
37+
38+
testImplementation(kotlin(Config.kotlinStdLib))
39+
testImplementation(projects.sentry)
40+
testImplementation(projects.sentrySystemTestSupport)
41+
testImplementation(libs.kotlin.test.junit)
42+
testImplementation(libs.slf4j.api)
43+
testImplementation(libs.slf4j.jdk14)
44+
}
45+
46+
// Configure the Shadow JAR (executable JAR with all dependencies)
47+
tasks.shadowJar {
48+
manifest { attributes["Main-Class"] = "io.sentry.samples.console.Main" }
49+
archiveClassifier.set("") // Remove the classifier so it replaces the regular JAR
50+
mergeServiceFiles()
1251
}
1352

14-
dependencies { implementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentless) }
53+
// Make the regular jar task depend on shadowJar
54+
tasks.jar {
55+
enabled = false
56+
dependsOn(tasks.shadowJar)
57+
}
58+
59+
// Fix the startScripts task dependency
60+
tasks.startScripts { dependsOn(tasks.shadowJar) }
61+
62+
configure<SourceSetContainer> { test { java.srcDir("src/test/java") } }
63+
64+
tasks.register<Test>("systemTest").configure {
65+
group = "verification"
66+
description = "Runs the System tests"
67+
68+
outputs.upToDateWhen { false }
69+
70+
maxParallelForks = 1
71+
72+
// Cap JVM args per test
73+
minHeapSize = "128m"
74+
maxHeapSize = "1g"
75+
76+
filter { includeTestsMatching("io.sentry.systemtest*") }
77+
}
78+
79+
tasks.named("test").configure {
80+
require(this is Test)
81+
82+
filter { excludeTestsMatching("io.sentry.systemtest.*") }
83+
}

sentry-samples/sentry-samples-console-opentelemetry-noagent/src/main/java/io/sentry/samples/console/Main.java

Lines changed: 14 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import io.opentelemetry.api.trace.Span;
55
import io.opentelemetry.api.trace.StatusCode;
66
import io.opentelemetry.context.Scope;
7+
import io.opentelemetry.sdk.OpenTelemetrySdk;
8+
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
79
import io.sentry.Breadcrumb;
810
import io.sentry.EventProcessor;
911
import io.sentry.Hint;
@@ -17,90 +19,22 @@
1719
import io.sentry.protocol.Message;
1820
import io.sentry.protocol.User;
1921
import java.util.Collections;
22+
import java.util.HashMap;
23+
import java.util.Map;
2024

2125
public class Main {
2226

2327
public static void main(String[] args) throws InterruptedException {
24-
Sentry.init(
25-
options -> {
26-
// NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in
27-
// your Sentry project/dashboard
28-
options.setDsn(
29-
"https://502f25099c204a2fbf4cb16edc5975d1@o447951.ingest.sentry.io/5428563");
30-
31-
// All events get assigned to the release. See more at
32-
// https://docs.sentry.io/workflow/releases/
33-
options.setRelease("io.sentry.samples.console@3.0.0+1");
34-
35-
// Modifications to event before it goes out. Could replace the event altogether
36-
options.setBeforeSend(
37-
(event, hint) -> {
38-
// Drop an event altogether:
39-
if (event.getTag("SomeTag") != null) {
40-
return null;
41-
}
42-
return event;
43-
});
44-
45-
options.setBeforeSendTransaction(
46-
(transaction, hint) -> {
47-
// Drop a transaction:
48-
if (transaction.getTag("SomeTransactionTag") != null) {
49-
return null;
50-
}
51-
52-
return transaction;
53-
});
54-
55-
// Allows inspecting and modifying, returning a new or simply rejecting (returning null)
56-
options.setBeforeBreadcrumb(
57-
(breadcrumb, hint) -> {
58-
// Don't add breadcrumbs with message containing:
59-
if (breadcrumb.getMessage() != null
60-
&& breadcrumb.getMessage().contains("bad breadcrumb")) {
61-
return null;
62-
}
63-
return breadcrumb;
64-
});
65-
66-
// Configure the background worker which sends events to sentry:
67-
// Wait up to 5 seconds before shutdown while there are events to send.
68-
options.setShutdownTimeoutMillis(5000);
69-
70-
// Enable SDK logging with Debug level
71-
options.setDebug(true);
72-
// To change the verbosity, use:
73-
// By default it's DEBUG.
74-
// options.setDiagnosticLevel(
75-
// SentryLevel
76-
// .ERROR); // A good option to have SDK debug log in prod is to use
77-
// only level
78-
// ERROR here.
79-
options.setEnablePrettySerializationOutput(false);
80-
81-
// Exclude frames from some packages from being "inApp" so are hidden by default in Sentry
82-
// UI:
83-
options.addInAppExclude("org.jboss");
84-
85-
// Include frames from our package
86-
options.addInAppInclude("io.sentry.samples");
87-
88-
// Performance configuration options
89-
// Set what percentage of traces should be collected
90-
options.setTracesSampleRate(1.0); // set 0.5 to send 50% of traces
91-
92-
// Determine traces sample rate based on the sampling context
93-
// options.setTracesSampler(
94-
// context -> {
95-
// // only 10% of transactions with "/product" prefix will be collected
96-
// if (!context.getTransactionContext().getName().startsWith("/products"))
97-
// {
98-
// return 0.1;
99-
// } else {
100-
// return 0.5;
101-
// }
102-
// });
103-
});
28+
AutoConfiguredOpenTelemetrySdk.builder()
29+
.setResultAsGlobal()
30+
.addPropertiesSupplier(() -> {
31+
final Map<String, String> properties = new HashMap<>();
32+
properties.put("otel.logs.exporter", "none");
33+
properties.put("otel.metrics.exporter", "none");
34+
properties.put("otel.traces.exporter", "none");
35+
return properties;
36+
})
37+
.build();
10438

10539
Sentry.addBreadcrumb(
10640
"A 'bad breadcrumb' that will be rejected because of 'BeforeBreadcrumb callback above.'");
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.sentry
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertTrue
5+
6+
class DummyTest {
7+
@Test
8+
fun `the only test`() {
9+
// only needed to have more than 0 tests and not fail the build
10+
assertTrue(true)
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package io.sentry.systemtest
2+
3+
import io.sentry.systemtest.util.TestHelper
4+
import java.util.concurrent.TimeUnit
5+
import org.junit.Assert.assertEquals
6+
import org.junit.Assert.assertTrue
7+
import org.junit.Before
8+
import org.junit.Test
9+
10+
class ConsoleApplicationSystemTest {
11+
lateinit var testHelper: TestHelper
12+
13+
@Before
14+
fun setup() {
15+
testHelper = TestHelper("http://localhost:8000")
16+
testHelper.reset()
17+
}
18+
19+
@Test
20+
fun `console application sends expected events when run as JAR`() {
21+
val jarFile = testHelper.findJar("sentry-samples-console-opentelemetry-noagent")
22+
val process =
23+
testHelper.launch(
24+
jarFile,
25+
mapOf(
26+
"SENTRY_DSN" to testHelper.dsn,
27+
// "SENTRY_AUTO_INIT" to "false",
28+
"SENTRY_TRACES_SAMPLE_RATE" to "1.0",
29+
"SENTRY_ENABLE_PRETTY_SERIALIZATION_OUTPUT" to "false",
30+
"SENTRY_DEBUG" to "true",
31+
"OTEL_METRICS_EXPORTER" to "none",
32+
"OTEL_LOGS_EXPORTER" to "none",
33+
"OTEL_TRACES_EXPORTER" to "none",
34+
),
35+
enableOtelAutoConfig = true
36+
)
37+
38+
process.waitFor(30, TimeUnit.SECONDS)
39+
assertEquals(0, process.exitValue())
40+
41+
// Verify that we received the expected events
42+
verifyExpectedEvents()
43+
}
44+
45+
private fun verifyExpectedEvents() {
46+
// Verify we received a "Fatal message!" event
47+
testHelper.ensureErrorReceived { event ->
48+
event.message?.formatted == "Fatal message!" && event.level?.name == "FATAL"
49+
}
50+
51+
// Verify we received a "Some warning!" event
52+
testHelper.ensureErrorReceived { event ->
53+
event.message?.formatted == "Some warning!" && event.level?.name == "WARNING"
54+
}
55+
56+
// Verify we received the RuntimeException
57+
testHelper.ensureErrorReceived { event ->
58+
event.exceptions?.any { ex -> ex.type == "RuntimeException" && ex.value == "Some error!" } ==
59+
true
60+
}
61+
62+
// Verify we received the detailed event with fingerprint
63+
testHelper.ensureErrorReceived { event ->
64+
event.message?.message == "Detailed event" &&
65+
event.fingerprints?.contains("NewClientDebug") == true &&
66+
event.level?.name == "DEBUG"
67+
}
68+
69+
// Verify we received transaction events
70+
testHelper.ensureTransactionReceived { transaction, _ ->
71+
transaction.transaction == "transaction name" &&
72+
transaction.spans?.any { span -> span.op == "child" } == true
73+
}
74+
75+
// Verify we received the loop messages (should be 10 of them)
76+
var loopMessageCount = 0
77+
try {
78+
for (i in 0..9) {
79+
testHelper.ensureErrorReceived { event ->
80+
val matches =
81+
event.message?.message?.contains("items we'll wait to flush to Sentry!") == true
82+
if (matches) loopMessageCount++
83+
matches
84+
}
85+
}
86+
} catch (e: Exception) {
87+
// Some loop messages might be missing, but we should have at least some
88+
}
89+
90+
assertTrue(
91+
"Should receive at least 5 loop messages, got $loopMessageCount",
92+
loopMessageCount >= 5,
93+
)
94+
95+
// Verify we have breadcrumbs
96+
testHelper.ensureErrorReceived { event ->
97+
event.breadcrumbs?.any { breadcrumb ->
98+
breadcrumb.message?.contains("Processed by") == true
99+
} == true
100+
}
101+
}
102+
}

sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/TestHelper.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,19 @@ class TestHelper(backendUrl: String) {
288288
return jarFiles.maxOf { it }
289289
}
290290

291-
fun launch(jar: File, env: Map<String, String>): Process {
291+
fun launch(jar: File, env: Map<String, String>, enableOtelAutoConfig: Boolean = false): Process {
292+
val processBuilderList = mutableListOf("java", "--add-opens", "java.base/java.lang=ALL-UNNAMED")
293+
294+
if (enableOtelAutoConfig) {
295+
processBuilderList.add("-Dotel.java.global-autoconfigure.enabled=true")
296+
}
297+
298+
processBuilderList.add("-jar")
299+
processBuilderList.add(jar.absolutePath)
300+
292301
val processBuilder =
293-
ProcessBuilder("java", "-jar", jar.absolutePath).inheritIO() // forward i/o to current process
302+
ProcessBuilder(processBuilderList)
303+
.inheritIO() // forward i/o to current process
294304

295305
processBuilder.environment().putAll(env)
296306

0 commit comments

Comments
 (0)