diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt index 6dd2143d95..82d2145ecc 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt @@ -35,4 +35,11 @@ object RageshakeConfig { * The maximum number of log lines a rageshake can contain. */ const val MAX_LOG_LINES_SIZE = 1_000_000 + + /** + * The maximum size of the description. The bug report server copies it verbatim into a GitHub issue body, + * which GitHub rejects above 65536 characters. The remaining budget is left to the other reported fields, + * mainly the list of the omitted log file names. + */ + const val MAX_DESCRIPTION_SIZE = 40 * 1024 } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt index 96bd7adfce..c1000784fc 100755 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt @@ -152,7 +152,7 @@ class DefaultBugReporter( append("\n\n\n\n--------------------------------- crash call stack ---------------------------------\n") append(crashCallStack) } - } + }.truncateDescription() val gzippedFiles = mutableListOf() var filesTooBig = emptyList() @@ -494,4 +494,13 @@ class DefaultBugReporter( private fun countLogLines(file: File): Int { return file.reader().useLines { it.count() } } + + private fun String.truncateDescription(): String { + return if (length <= RageshakeConfig.MAX_DESCRIPTION_SIZE) { + this + } else { + take(RageshakeConfig.MAX_DESCRIPTION_SIZE) + + "\n\n--------------------------------- truncated, see the attached logs ---------------------------------" + } + } } diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt index ad29e5ed3f..fe32678c27 100755 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt @@ -309,6 +309,68 @@ class DefaultBugReporterTest : RobolectricTest() { assertThat(foundValues["label"]).isEqualTo("crash") } + @Test + fun `test sendBugReport truncates a description which is too long`() = runTest { + val server = MockWebServer() + server.enqueue( + MockResponse() + .setResponseCode(200) + ) + server.start() + val hugeCrashCallStack = "a".repeat(RageshakeConfig.MAX_DESCRIPTION_SIZE * 2) + val sut = createDefaultBugReporter( + server = server, + crashDataStore = FakeCrashDataStore(hugeCrashCallStack, true), + ) + + sut.sendBugReport( + withDevicesLogs = true, + withCrashLogs = true, + withScreenshot = true, + problemDescription = "a bug occurred", + canContact = true, + listener = NoopBugReporterListener(), + ) + val request = server.takeRequest() + + val text = collectValuesFromFormData(request)["text"]!! + assertThat(text).startsWith("a bug occurred") + assertThat(text).contains("truncated") + assertThat(text.length).isAtMost(RageshakeConfig.MAX_DESCRIPTION_SIZE + TRUNCATION_MARKER_MAX_SIZE) + assertThat(text.length).isLessThan(GITHUB_MAX_ISSUE_BODY_SIZE) + server.shutdown() + } + + @Test + fun `test sendBugReport does not truncate a description which fits`() = runTest { + val server = MockWebServer() + server.enqueue( + MockResponse() + .setResponseCode(200) + ) + server.start() + val sut = createDefaultBugReporter( + server = server, + crashDataStore = FakeCrashDataStore("I did crash", true), + ) + + sut.sendBugReport( + withDevicesLogs = true, + withCrashLogs = true, + withScreenshot = true, + problemDescription = "a bug occurred", + canContact = true, + listener = NoopBugReporterListener(), + ) + val request = server.takeRequest() + + val text = collectValuesFromFormData(request)["text"]!! + assertThat(text).startsWith("a bug occurred") + assertThat(text).endsWith("I did crash") + assertThat(text).doesNotContain("truncated") + server.shutdown() + } + private fun collectValuesFromFormData(request: RecordedRequest): HashMap { val boundary = request.headers["Content-Type"]!!.split("=").last() val foundValues = HashMap() @@ -535,5 +597,7 @@ class DefaultBugReporterTest : RobolectricTest() { companion object { private const val EXPECTED_NUMBER_OF_PROGRESS_VALUE = 18 + private const val GITHUB_MAX_ISSUE_BODY_SIZE = 65536 + private const val TRUNCATION_MARKER_MAX_SIZE = 200 } }