Merge pull request #66 from vector-im/feature/bma/sonar
Sonar / Kover / Codecov
This commit is contained in:
commit
95b8cb6c03
14 changed files with 156 additions and 16 deletions
25
.github/workflows/tests.yml
vendored
25
.github/workflows/tests.yml
vendored
|
|
@ -23,6 +23,17 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: ./gradlew test $CI_GRADLE_ARG_PROPERTIES
|
run: ./gradlew test $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
- name: Generate kover report
|
||||||
|
if: always()
|
||||||
|
run: ./gradlew koverMergedReport $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
|
- name: Archive kover report
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: kover-results
|
||||||
|
path: |
|
||||||
|
**/build/reports/kover/merged
|
||||||
|
|
||||||
- name: Archive test results on error
|
- name: Archive test results on error
|
||||||
if: failure()
|
if: failure()
|
||||||
|
|
@ -32,3 +43,17 @@ jobs:
|
||||||
path: |
|
path: |
|
||||||
**/out/failures/
|
**/out/failures/
|
||||||
**/build/reports/tests/*UnitTest/
|
**/build/reports/tests/*UnitTest/
|
||||||
|
|
||||||
|
- name: Publish results to Sonar
|
||||||
|
env:
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
if: ${{ always() && env.SONAR_TOKEN != '' && env.ORG_GRADLE_PROJECT_SONAR_LOGIN != '' }}
|
||||||
|
run: ./gradlew sonar $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
|
||||||
|
# https://github.com/codecov/codecov-action
|
||||||
|
- name: Upload coverage reports to codecov
|
||||||
|
if: always()
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
# with:
|
||||||
|
# files: build/reports/kover/merged/xml/report.xml
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,11 @@
|
||||||
|
[](https://github.com/vector-im/element-x-android/actions/workflows/build.yml?query=branch%3Adevelop)
|
||||||
|
[](https://sonarcloud.io/summary/new_code?id=vector-im_element-x-android)
|
||||||
|
[](https://sonarcloud.io/summary/new_code?id=vector-im_element-x-android)
|
||||||
|
[](https://sonarcloud.io/summary/new_code?id=vector-im_element-x-android)
|
||||||
|
[](https://codecov.io/github/vector-im/element-x-android)
|
||||||
|
[](https://matrix.to/#/#element-android:matrix.org)
|
||||||
|
[](https://translate.element.io/engage/element-android/?utm_source=widget)
|
||||||
|
|
||||||
# element-x-android-poc
|
# element-x-android-poc
|
||||||
|
|
||||||
Proof Of Concept to run a Matrix client on Android devices using the Matrix Rust Sdk and Jetpack compose.
|
Proof Of Concept to run a Matrix client on Android devices using the Matrix Rust Sdk and Jetpack compose.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2022 New Vector Ltd
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ plugins {
|
||||||
alias(libs.plugins.detekt)
|
alias(libs.plugins.detekt)
|
||||||
alias(libs.plugins.ktlint)
|
alias(libs.plugins.ktlint)
|
||||||
alias(libs.plugins.dependencygraph)
|
alias(libs.plugins.dependencygraph)
|
||||||
|
alias(libs.plugins.sonarqube)
|
||||||
|
alias(libs.plugins.kover)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Delete>("clean").configure {
|
tasks.register<Delete>("clean").configure {
|
||||||
|
|
@ -108,3 +110,68 @@ allprojects {
|
||||||
plugin("org.owasp.dependencycheck")
|
plugin("org.owasp.dependencycheck")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// To run a sonar analysis:
|
||||||
|
// Run './gradlew sonar -Dsonar.login=<SONAR_LOGIN>'
|
||||||
|
// The SONAR_LOGIN is stored in passbolt as Token Sonar Cloud Bma
|
||||||
|
// Sonar result can be found here: https://sonarcloud.io/project/overview?id=vector-im_element-x-android
|
||||||
|
sonar {
|
||||||
|
properties {
|
||||||
|
property("sonar.projectName", "element-x-android")
|
||||||
|
property("sonar.projectKey", "vector-im_element-x-android")
|
||||||
|
property("sonar.host.url", "https://sonarcloud.io")
|
||||||
|
property("sonar.projectVersion", "1.0") // TODO project(":app").android.defaultConfig.versionName)
|
||||||
|
property("sonar.sourceEncoding", "UTF-8")
|
||||||
|
property("sonar.links.homepage", "https://github.com/vector-im/element-x-android/")
|
||||||
|
property("sonar.links.ci", "https://github.com/vector-im/element-x-android/actions")
|
||||||
|
property("sonar.links.scm", "https://github.com/vector-im/element-x-android/")
|
||||||
|
property("sonar.links.issue", "https://github.com/vector-im/element-x-android/issues")
|
||||||
|
property("sonar.organization", "new_vector_ltd_organization")
|
||||||
|
property("sonar.login", if (project.hasProperty("SONAR_LOGIN")) project.property("SONAR_LOGIN")!! else "invalid")
|
||||||
|
|
||||||
|
// exclude source code from analyses separated by a colon (:)
|
||||||
|
// Exclude Java source
|
||||||
|
property("sonar.exclusions", "**/BugReporterMultipartBody.java")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
val projectDir = projectDir.toString()
|
||||||
|
sonar {
|
||||||
|
properties {
|
||||||
|
// Note: folders `kotlin` are not supported (yet), I asked on their side: https://community.sonarsource.com/t/82824
|
||||||
|
// As a workaround provide the path in `sonar.sources` property.
|
||||||
|
if (File("$projectDir/src/main/kotlin").exists()) {
|
||||||
|
property("sonar.sources", "src/main/kotlin")
|
||||||
|
}
|
||||||
|
if (File("$projectDir/src/test/kotlin").exists()) {
|
||||||
|
property("sonar.tests", "src/test/kotlin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
apply(plugin = "kover")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run `./gradlew koverMergedHtmlReport` to get report at ./build/reports/kover
|
||||||
|
// Run `./gradlew koverMergedReport` to also get XML report
|
||||||
|
koverMerged {
|
||||||
|
enable()
|
||||||
|
|
||||||
|
filters {
|
||||||
|
classes {
|
||||||
|
excludes.addAll(
|
||||||
|
listOf(
|
||||||
|
/*
|
||||||
|
"*Fragment",
|
||||||
|
"*Fragment\$*",
|
||||||
|
"*Activity",
|
||||||
|
"*Activity\$*",
|
||||||
|
*/
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ dependencies {
|
||||||
implementation(projects.libraries.designsystem)
|
implementation(projects.libraries.designsystem)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.testtags)
|
implementation(projects.libraries.testtags)
|
||||||
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(libs.accompanist.pager)
|
implementation(libs.accompanist.pager)
|
||||||
implementation(libs.accompanist.pagerindicator)
|
implementation(libs.accompanist.pagerindicator)
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ package io.element.android.features.rageshake.logs
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import io.element.android.libraries.androidutils.file.safeDelete
|
||||||
import io.element.android.libraries.core.data.tryOrNull
|
import io.element.android.libraries.core.data.tryOrNull
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
|
@ -37,6 +39,7 @@ import java.util.logging.Logger
|
||||||
class VectorFileLogger(
|
class VectorFileLogger(
|
||||||
context: Context,
|
context: Context,
|
||||||
// private val vectorPreferences: VectorPreferences
|
// private val vectorPreferences: VectorPreferences
|
||||||
|
private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||||
) : Timber.Tree() {
|
) : Timber.Tree() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -82,7 +85,7 @@ class VectorFileLogger(
|
||||||
|
|
||||||
for (i in 0..15) {
|
for (i in 0..15) {
|
||||||
val file = File(cacheDirectory, "elementLogs.${i}.txt")
|
val file = File(cacheDirectory, "elementLogs.${i}.txt")
|
||||||
tryOrNull { file.delete() }
|
file.safeDelete()
|
||||||
}
|
}
|
||||||
|
|
||||||
fileHandler = tryOrNull(
|
fileHandler = tryOrNull(
|
||||||
|
|
@ -101,14 +104,14 @@ class VectorFileLogger(
|
||||||
fun reset() {
|
fun reset() {
|
||||||
// Delete all files
|
// Delete all files
|
||||||
getLogFiles().map {
|
getLogFiles().map {
|
||||||
tryOrNull { it.delete() }
|
it.safeDelete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||||
fileHandler ?: return
|
fileHandler ?: return
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(dispatcher) {
|
||||||
if (skipLog(priority)) return@launch
|
if (skipLog(priority)) return@launch
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
logToFile(t)
|
logToFile(t)
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,12 @@ import io.element.android.features.rageshake.crash.CrashDataStore
|
||||||
import io.element.android.features.rageshake.logs.VectorFileLogger
|
import io.element.android.features.rageshake.logs.VectorFileLogger
|
||||||
import io.element.android.features.rageshake.screenshot.ScreenshotHolder
|
import io.element.android.features.rageshake.screenshot.ScreenshotHolder
|
||||||
import io.element.android.libraries.androidutils.file.compressFile
|
import io.element.android.libraries.androidutils.file.compressFile
|
||||||
|
import io.element.android.libraries.androidutils.file.safeDelete
|
||||||
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.core.extensions.toOnOff
|
import io.element.android.libraries.core.extensions.toOnOff
|
||||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -54,6 +55,7 @@ class BugReporter @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val screenshotHolder: ScreenshotHolder,
|
private val screenshotHolder: ScreenshotHolder,
|
||||||
private val crashDataStore: CrashDataStore,
|
private val crashDataStore: CrashDataStore,
|
||||||
|
private val coroutineDispatchers: CoroutineDispatchers,
|
||||||
/*
|
/*
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val versionProvider: VersionProvider,
|
private val versionProvider: VersionProvider,
|
||||||
|
|
@ -168,7 +170,7 @@ class BugReporter @Inject constructor(
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
var serverError: String? = null
|
var serverError: String? = null
|
||||||
var reportURL: String? = null
|
var reportURL: String? = null
|
||||||
withContext(Dispatchers.IO) {
|
withContext(coroutineDispatchers.io) {
|
||||||
var bugDescription = theBugDescription
|
var bugDescription = theBugDescription
|
||||||
val crashCallStack = crashDataStore.crashInfo().first()
|
val crashCallStack = crashDataStore.crashInfo().first()
|
||||||
|
|
||||||
|
|
@ -418,12 +420,12 @@ class BugReporter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(coroutineDispatchers.main) {
|
||||||
mBugReportCall = null
|
mBugReportCall = null
|
||||||
|
|
||||||
// delete when the bug report has been successfully sent
|
// delete when the bug report has been successfully sent
|
||||||
for (file in mBugReportFiles) {
|
for (file in mBugReportFiles) {
|
||||||
file.delete()
|
file.safeDelete()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null != listener) {
|
if (null != listener) {
|
||||||
|
|
@ -498,7 +500,7 @@ class BugReporter @Inject constructor(
|
||||||
val logCatErrFile = File(context.cacheDir.absolutePath, if (isErrorLogcat) LOG_CAT_ERROR_FILENAME else LOG_CAT_FILENAME)
|
val logCatErrFile = File(context.cacheDir.absolutePath, if (isErrorLogcat) LOG_CAT_ERROR_FILENAME else LOG_CAT_FILENAME)
|
||||||
|
|
||||||
if (logCatErrFile.exists()) {
|
if (logCatErrFile.exists()) {
|
||||||
logCatErrFile.delete()
|
logCatErrFile.safeDelete()
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package io.element.android.features.rageshake.screenshot
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import io.element.android.libraries.androidutils.bitmap.writeBitmap
|
import io.element.android.libraries.androidutils.bitmap.writeBitmap
|
||||||
|
import io.element.android.libraries.androidutils.file.safeDelete
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
import io.element.android.libraries.di.SingleIn
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
|
@ -38,6 +39,6 @@ class ScreenshotHolder @Inject constructor(
|
||||||
fun getFile() = file.takeIf { it.exists() && it.length() > 0 }
|
fun getFile() = file.takeIf { it.exists() && it.length() > 0 }
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
file.delete()
|
file.safeDelete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,3 +144,5 @@ dependencycheck = { id = "org.owasp.dependencycheck", version.ref = "dependencyc
|
||||||
stem = { id = "com.likethesalad.stem", version.ref = "stem" }
|
stem = { id = "com.likethesalad.stem", version.ref = "stem" }
|
||||||
stemlibrary = { id = "com.likethesalad.stem-library", version.ref = "stem" }
|
stemlibrary = { id = "com.likethesalad.stem-library", version.ref = "stem" }
|
||||||
paparazzi = "app.cash.paparazzi:1.2.0"
|
paparazzi = "app.cash.paparazzi:1.2.0"
|
||||||
|
sonarqube = "org.sonarqube:3.5.0.2730"
|
||||||
|
kover = "org.jetbrains.kotlinx.kover:0.6.1"
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,5 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.timber)
|
implementation(libs.timber)
|
||||||
implementation(libs.androidx.corektx)
|
implementation(libs.androidx.corektx)
|
||||||
|
implementation(projects.libraries.core)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.androidutils.file
|
||||||
|
|
||||||
|
import io.element.android.libraries.core.data.tryOrNull
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun File.safeDelete() {
|
||||||
|
tryOrNull(
|
||||||
|
onError = {
|
||||||
|
Timber.e(it, "Error, unable to delete file $path")
|
||||||
|
},
|
||||||
|
operation = {
|
||||||
|
if (delete().not()) {
|
||||||
|
Timber.w("Warning, unable to delete file $path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ fun compressFile(file: File): File? {
|
||||||
val dstFile = file.resolveSibling(file.name + ".gz")
|
val dstFile = file.resolveSibling(file.name + ".gz")
|
||||||
|
|
||||||
if (dstFile.exists()) {
|
if (dstFile.exists()) {
|
||||||
dstFile.delete()
|
dstFile.safeDelete()
|
||||||
}
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ internal class RustMatrixClient internal constructor(
|
||||||
.addView(slidingSyncView)
|
.addView(slidingSyncView)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private val slidingSyncObserverProxy = SlidingSyncObserverProxy(coroutineScope, dispatchers)
|
private val slidingSyncObserverProxy = SlidingSyncObserverProxy(coroutineScope)
|
||||||
private val roomSummaryDataSource: RustRoomSummaryDataSource =
|
private val roomSummaryDataSource: RustRoomSummaryDataSource =
|
||||||
RustRoomSummaryDataSource(
|
RustRoomSummaryDataSource(
|
||||||
slidingSyncObserverProxy.updateSummaryFlow,
|
slidingSyncObserverProxy.updateSummaryFlow,
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.sync
|
package io.element.android.libraries.matrix.sync
|
||||||
|
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
|
@ -30,7 +29,6 @@ private const val BUFFER_SIZE = 64
|
||||||
|
|
||||||
class SlidingSyncObserverProxy(
|
class SlidingSyncObserverProxy(
|
||||||
private val coroutineScope: CoroutineScope,
|
private val coroutineScope: CoroutineScope,
|
||||||
private val coroutineDispatchers: CoroutineDispatchers
|
|
||||||
) : SlidingSyncObserver {
|
) : SlidingSyncObserver {
|
||||||
|
|
||||||
private val updateSummaryMutableFlow =
|
private val updateSummaryMutableFlow =
|
||||||
|
|
@ -39,7 +37,7 @@ class SlidingSyncObserverProxy(
|
||||||
|
|
||||||
override fun didReceiveSyncUpdate(summary: UpdateSummary) {
|
override fun didReceiveSyncUpdate(summary: UpdateSummary) {
|
||||||
if (summary.rooms.isEmpty()) return
|
if (summary.rooms.isEmpty()) return
|
||||||
coroutineScope.launch(coroutineDispatchers.io) {
|
coroutineScope.launch {
|
||||||
updateSummaryMutableFlow.emit(summary)
|
updateSummaryMutableFlow.emit(summary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue