Skip to content

Commit 837a64f

Browse files
test(critical): Add Mock Relay to verify crash envelope
1 parent 5dda9c3 commit 837a64f

File tree

15 files changed

+250
-4
lines changed

15 files changed

+250
-4
lines changed

.github/workflows/integration-tests-ui-critical.yml

+32-1
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ concurrency:
1111
cancel-in-progress: true
1212

1313
env:
14-
BASE_PATH: "sentry-android-integration-tests/sentry-uitest-android-critical"
14+
INTEGRATION_TESTS_PATH: "sentry-android-integration-tests"
15+
BASE_PATH: "${{env.INTEGRATION_TESTS_PATH}}/sentry-uitest-android-critical"
1516
BUILD_PATH: "build/outputs/apk/release"
1617
APK_NAME: "sentry-uitest-android-critical-release.apk"
1718
APK_ARTIFACT_NAME: "sentry-uitest-android-critical-release"
1819
MAESTRO_VERSION: "1.39.0"
20+
MOCK_RELAY_ARTIFACT_NAME: "sentry-mock-relay"
21+
MOCK_RELAY_PATH: "sentry-mock-relay-0.0.1.zip"
1922

2023
jobs:
2124
build:
@@ -36,6 +39,16 @@ jobs:
3639
with:
3740
gradle-home-cache-cleanup: true
3841

42+
- name: Build mock relay
43+
run: make buildMockRelay
44+
45+
- name: Upload Mock Relay
46+
uses: actions/upload-artifact@v4
47+
with:
48+
name: ${{env.MOCK_RELAY_ARTIFACT_NAME}}
49+
path: "${{env.INTEGRATION_TESTS_PATH}}/sentry-mock-relay/build/distributions/${{env.MOCK_RELAY_PATH}}"
50+
retention-days: 1
51+
3952
- name: Build debug APK
4053
run: make assembleUiTestCriticalRelease
4154

@@ -91,6 +104,14 @@ jobs:
91104
with:
92105
name: ${{env.APK_ARTIFACT_NAME}}
93106

107+
- name: Download Mock Relay
108+
uses: actions/download-artifact@v4
109+
with:
110+
name: ${{env.MOCK_RELAY_ARTIFACT_NAME}}
111+
112+
- name: Unzip Mock Relay
113+
run: unzip -o ${{env.MOCK_RELAY_PATH}}
114+
94115
- name: Install Maestro
95116
uses: dniHze/maestro-test-action@bda8a93211c86d0a05b7a4597c5ad134566fbde4 # [email protected]
96117
with:
@@ -117,7 +138,17 @@ jobs:
117138
-timezone US/Pacific
118139
script: |
119140
adb install -r -d "${{env.APK_NAME}}"
141+
./sentry-mock-relay-0.0.1/bin/sentry-mock-relay > /dev/null & MOCK_RELAY_PID=$!
142+
143+
set +e
120144
maestro test "${{env.BASE_PATH}}/maestro" --debug-output "${{env.BASE_PATH}}/maestro-logs"
145+
MAESTRO_EXIT_CODE=$?
146+
147+
curl --fail http://localhost:8961/assertReceivedAtLeastOneCrashReport
148+
MOCK_RELAY_EXIT_CODE=$?
149+
150+
kill $MOCK_RELAY_PID
151+
exit $(($MAESTRO_EXIT_CODE || $MOCK_RELAY_EXIT_CODE))
121152
122153
- name: Upload Maestro test results
123154
if: failure()

.github/workflows/system-tests-backend.yml

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ jobs:
5959
-e '/.*"sentry-android-integration-tests:sentry-uitest-android-benchmark",/d' \
6060
-e '/.*"sentry-android-integration-tests:sentry-uitest-android",/d' \
6161
-e '/.*"sentry-android-integration-tests:sentry-uitest-android-critical",/d' \
62+
-e '/.*"sentry-android-integration-tests:sentry-mock-relay",/d' \
6263
-e '/.*"sentry-android-integration-tests:test-app-sentry",/d' \
6364
-e '/.*"sentry-samples:sentry-samples-android",/d' \
6465
-e '/.*"sentry-android-replay",/d' \
@@ -70,6 +71,7 @@ jobs:
7071
-e '/.*"sentry-uitest-android",/d' \
7172
-e '/.*"sentry-uitest-android-benchmark",/d' \
7273
-e '/.*"sentry-uitest-android-critical",/d' \
74+
-e '/.*"sentry-mock-relay",/d' \
7375
-e '/.*"test-app-sentry",/d' \
7476
-e '/.*"sentry-samples-android",/d' \
7577
build.gradle.kts

Makefile

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: all clean compile javadocs dryRelease update stop checkFormat format api assembleBenchmarkTestRelease assembleUiTestRelease assembleUiTestCriticalRelease createCoverageReports runUiTestCritical check preMerge publish
1+
.PHONY: all clean compile javadocs dryRelease update stop checkFormat format api assembleBenchmarkTestRelease assembleUiTestRelease assembleUiTestCriticalRelease createCoverageReports runUiTestCritical buildMockRelay check preMerge publish
22

33
all: stop clean javadocs compile createCoverageReports
44
assembleBenchmarks: assembleBenchmarkTestRelease
@@ -61,6 +61,10 @@ assembleUiTestCriticalRelease:
6161
runUiTestCritical:
6262
./scripts/test-ui-critical.sh
6363

64+
# Build the mock relay for critical tests
65+
buildMockRelay:
66+
./gradlew :sentry-android-integration-tests:sentry-mock-relay:build
67+
6468
# Create coverage reports
6569
# - Jacoco for Java & Android modules
6670
# - Kover for KMP modules e.g sentry-compose

build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ apiValidation {
6969
"sentry-uitest-android",
7070
"sentry-uitest-android-benchmark",
7171
"sentry-uitest-android-critical",
72+
"sentry-mock-relay",
7273
"test-app-plain",
7374
"test-app-sentry",
7475
"sentry-samples-netflix-dgs"

buildSrc/src/main/java/Config.kt

+4
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,10 @@ object Config {
201201
val javaFaker = "com.github.javafaker:javafaker:1.0.2"
202202
val msgpack = "org.msgpack:msgpack-core:0.9.8"
203203
val leakCanaryInstrumentation = "com.squareup.leakcanary:leakcanary-android-instrumentation:2.14"
204+
val kotlinxSerializationJson = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1"
205+
val pluginSerializationVersion = "1.5.0"
206+
val logbackClassic = "ch.qos.logback:logback-classic:1.4.14"
207+
val ktorVersion = "2.3.12"
204208
}
205209

206210
object QualityPlugins {

scripts/test-ui-critical.sh

+20
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ if ! command -v maestro &> /dev/null; then
1919
exit 1
2020
fi
2121

22+
echo "Building the mock relay..."
23+
make buildMockRelay
24+
2225
echo "Building the UI Test Critical app..."
2326
make assembleUiTestCriticalRelease
2427

@@ -29,7 +32,24 @@ apkName="sentry-uitest-android-critical-release.apk"
2932
appPath="${baseDir}/${buildDir}/${apkName}"
3033
adb install -r -d "$appPath"
3134

35+
echo "Starting the mock relay..."
36+
cd sentry-android-integration-tests/sentry-mock-relay/build/distributions
37+
unzip -o sentry-mock-relay-0.0.1.zip
38+
./sentry-mock-relay-0.0.1/bin/sentry-mock-relay > /dev/null & MOCK_RELAY_PID=$!
39+
echo "Mock relay PID: $MOCK_RELAY_PID"
40+
41+
set +e
3242
echo "Running the Maestro tests..."
3343
maestro test \
3444
"${baseDir}/maestro" \
3545
--debug-output "${baseDir}/maestro-logs"
46+
MAESTRO_EXIT_CODE=$?
47+
48+
echo "Checking mock relay results..."
49+
curl --fail http://localhost:8961/assertReceivedAtLeastOneCrashReport
50+
MOCK_RELAY_EXIT_CODE=$?
51+
52+
echo "Stopping the mock relay..."
53+
kill $MOCK_RELAY_PID
54+
55+
exit $(($MAESTRO_EXIT_CODE || $MOCK_RELAY_EXIT_CODE))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
.gradle
2+
build/
3+
!gradle/wrapper/gradle-wrapper.jar
4+
!**/src/main/**/build/
5+
!**/src/test/**/build/
6+
7+
### STS ###
8+
.apt_generated
9+
.classpath
10+
.factorypath
11+
.project
12+
.settings
13+
.springBeans
14+
.sts4-cache
15+
bin/
16+
!**/src/main/**/bin/
17+
!**/src/test/**/bin/
18+
19+
### IntelliJ IDEA ###
20+
.idea
21+
*.iws
22+
*.iml
23+
*.ipr
24+
out/
25+
!**/src/main/**/out/
26+
!**/src/test/**/out/
27+
28+
### NetBeans ###
29+
/nbproject/private/
30+
/nbbuild/
31+
/dist/
32+
/nbdist/
33+
/.nb-gradle/
34+
35+
### VS Code ###
36+
.vscode/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
plugins {
3+
kotlin("jvm")
4+
id("io.ktor.plugin") version Config.TestLibs.ktorVersion
5+
kotlin("plugin.serialization") version Config.TestLibs.pluginSerializationVersion
6+
}
7+
8+
group = "io.sentry.mock-relay"
9+
version = "0.0.1"
10+
11+
application {
12+
mainClass.set("io.ktor.server.netty.EngineMain")
13+
14+
val isDevelopment: Boolean = project.ext.has("development")
15+
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
16+
}
17+
18+
repositories {
19+
mavenCentral()
20+
}
21+
22+
dependencies {
23+
implementation(Config.TestLibs.kotlinxSerializationJson)
24+
implementation("io.ktor:ktor-server-core-jvm")
25+
implementation("io.ktor:ktor-server-netty-jvm")
26+
implementation(Config.TestLibs.logbackClassic)
27+
implementation("io.ktor:ktor-server-config-yaml")
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package io.sentry
2+
3+
import io.ktor.server.request.receive
4+
import io.ktor.server.request.receiveText
5+
import io.ktor.server.request.uri
6+
import io.ktor.server.response.respondText
7+
import io.ktor.server.routing.get
8+
import io.ktor.server.routing.post
9+
import io.ktor.server.routing.routing
10+
import kotlinx.serialization.json.Json
11+
import kotlinx.serialization.json.JsonElement
12+
import kotlinx.serialization.json.jsonArray
13+
import kotlinx.serialization.json.jsonObject
14+
import kotlinx.serialization.json.jsonPrimitive
15+
import java.util.zip.GZIPInputStream
16+
import io.ktor.server.application.Application
17+
import io.ktor.server.application.call
18+
19+
fun main(args: Array<String>) {
20+
io.ktor.server.netty.EngineMain.main(args)
21+
}
22+
23+
fun Application.module() {
24+
configureRouting()
25+
}
26+
27+
fun Application.configureRouting() {
28+
val receivedEnvelopes = mutableListOf<List<JsonElement>>()
29+
30+
routing {
31+
post("/{...}") {
32+
println("Received request: ${call.request.uri}")
33+
val textBody: String = if (call.request.headers["Content-Encoding"] == "gzip") {
34+
call.receive<ByteArray>().let {
35+
GZIPInputStream(it.inputStream()).bufferedReader().use { reader ->
36+
reader.readText()
37+
}
38+
}
39+
} else {
40+
call.receiveText()
41+
}
42+
43+
val jsonItems = textBody.split('\n').mapNotNull { line ->
44+
try {
45+
Json.parseToJsonElement(line)
46+
} catch (e: Exception) {
47+
null
48+
}
49+
}
50+
51+
receivedEnvelopes.add(jsonItems)
52+
53+
call.respondText("{}")
54+
}
55+
get("/healthCheck") {
56+
call.respondText("OK")
57+
}
58+
get("/assertReceivedAtLeastOneCrashReport") {
59+
if (receivedEnvelopes.isEmpty()) {
60+
call.respondText("Mocked Replay have not received any envelopes", status = io.ktor.http.HttpStatusCode.BadRequest)
61+
}
62+
63+
val hasCrashReport = receivedEnvelopes.any { envelope ->
64+
envelope.any { item ->
65+
try {
66+
if (item.jsonObject.containsKey("exception")) {
67+
val exception = item.jsonObject["exception"]?.jsonObject
68+
val values = exception?.get("values")?.jsonArray
69+
values?.any { value ->
70+
val message = value.jsonObject["value"]?.jsonPrimitive?.content
71+
message == "Crash the test app."
72+
} ?: false
73+
} else {
74+
false
75+
}
76+
} catch (e: Exception) {
77+
false
78+
}
79+
}
80+
}
81+
82+
if (hasCrashReport) {
83+
call.respondText("Received at least one crash report")
84+
} else {
85+
call.respondText("No crash report received", status = io.ktor.http.HttpStatusCode.BadRequest)
86+
}
87+
}
88+
}
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
ktor:
2+
application:
3+
modules:
4+
- io.sentry.ApplicationKt.module
5+
deployment:
6+
port: 8961
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<configuration>
2+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
3+
<encoder>
4+
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
5+
</encoder>
6+
</appender>
7+
<root level="trace">
8+
<appender-ref ref="STDOUT"/>
9+
</root>
10+
<logger name="org.eclipse.jetty" level="INFO"/>
11+
<logger name="io.netty" level="INFO"/>
12+
</configuration>

sentry-android-integration-tests/sentry-uitest-android-critical/build.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ android {
5151

5252
dependencies {
5353
implementation(kotlin(Config.kotlinStdLib, org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION))
54+
implementation(Config.Libs.okhttp)
55+
implementation(Config.TestLibs.mockWebserver)
5456
implementation(Config.Libs.androidxCore)
5557
implementation(Config.Libs.composeActivity)
5658
implementation(Config.Libs.composeFoundation)

sentry-android-integration-tests/sentry-uitest-android-critical/src/main/AndroidManifest.xml

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
<application
66
android:label="Sentry UI Tests Critical"
77
android:supportsRtl="true"
8-
tools:targetApi="31">
9-
<meta-data android:name="io.sentry.dsn" android:value="https://[email protected]/5428559" />
8+
tools:targetApi="31"
9+
android:networkSecurityConfig="@xml/network"
10+
>
11+
<meta-data android:name="io.sentry.dsn" android:value="http://[email protected]:8961/dsn" />
1012
<meta-data android:name="io.sentry.debug" android:value="true" />
1113
<activity
1214
android:name=".MainActivity"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<network-security-config>
3+
<domain-config cleartextTrafficPermitted="true">
4+
<!-- Allow cleartext traffic from the emulator to the host machine -->
5+
<!-- See https://developer.android.com/studio/run/emulator-networking for more details -->
6+
<domain includeSubdomains="true">10.0.2.2</domain>
7+
</domain-config>
8+
</network-security-config>

settings.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ include(
6262
"sentry-samples:sentry-samples-spring-boot-webflux-jakarta",
6363
"sentry-samples:sentry-samples-netflix-dgs",
6464
"sentry-android-integration-tests:sentry-uitest-android-critical",
65+
"sentry-android-integration-tests:sentry-mock-relay",
6566
"sentry-android-integration-tests:sentry-uitest-android-benchmark",
6667
"sentry-android-integration-tests:sentry-uitest-android",
6768
"sentry-android-integration-tests:test-app-plain",

0 commit comments

Comments
 (0)