Skip to content

Commit fd4935f

Browse files
Merge branch 'develop' into release-process
# Conflicts: # build.gradle.kts # gradle/libs.versions.toml
2 parents bc0529b + 01b38cb commit fd4935f

File tree

10 files changed

+208
-37
lines changed

10 files changed

+208
-37
lines changed

.github/workflows/android.yml

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
name: Android CI
2+
3+
on:
4+
push:
5+
branches:
6+
- develop
7+
- main
8+
9+
pull_request:
10+
branches:
11+
- '**'
12+
13+
workflow_dispatch:
14+
15+
concurrency:
16+
group: ${{ github.workflow }}-${{ github.ref }}
17+
cancel-in-progress: true
18+
19+
env:
20+
BUILD_CACHE_AWS_REGION: ${{ secrets.BUILD_CACHE_AWS_REGION }}
21+
BUILD_CACHE_AWS_BUCKET: ${{ secrets.BUILD_CACHE_AWS_BUCKET }}
22+
BUILD_CACHE_AWS_ACCESS_KEY_ID: ${{ secrets.BUILD_CACHE_AWS_ACCESS_KEY_ID }}
23+
BUILD_CACHE_AWS_SECRET_KEY: ${{ secrets.BUILD_CACHE_AWS_SECRET_KEY }}
24+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25+
GITHUB_PR_NUM: ${{ github.event.pull_request.number }}
26+
27+
jobs:
28+
build:
29+
name: Compile
30+
runs-on: ubuntu-latest
31+
steps:
32+
- uses: actions/checkout@v3
33+
34+
- uses: GetStream/android-ci-actions/actions/setup-java@main
35+
36+
- uses: GetStream/android-ci-actions/actions/gradle-cache@main
37+
38+
- name: Make Gradle executable
39+
run: chmod +x ./gradlew
40+
41+
- name: Build with Gradle
42+
run: ./gradlew assembleDebug --scan
43+
44+
spotless:
45+
name: Spotless
46+
runs-on: ubuntu-latest
47+
steps:
48+
- name: Check out code
49+
uses: actions/[email protected]
50+
- uses: GetStream/android-ci-actions/actions/setup-java@main
51+
- name: spotless
52+
run: ./gradlew spotlessCheck --scan
53+
lint:
54+
name: Lint
55+
runs-on: ubuntu-latest
56+
steps:
57+
- name: Check out code
58+
uses: actions/[email protected]
59+
- uses: GetStream/android-ci-actions/actions/setup-java@main
60+
- name: spotless
61+
run: ./gradlew lint
62+
63+
unitTest:
64+
name: Unit Tests
65+
runs-on: ubuntu-latest
66+
67+
steps:
68+
- name: Checkout
69+
uses: actions/checkout@v3
70+
71+
- uses: GetStream/android-ci-actions/actions/setup-java@main
72+
73+
- uses: GetStream/android-ci-actions/actions/gradle-cache@main
74+
75+
- name: Run unit tests
76+
run: ./gradlew :stream-android-core:koverXmlReportDebug --scan --stacktrace
77+
78+
- name: Unit tests core results
79+
uses: actions/upload-artifact@v4
80+
if: failure()
81+
with:
82+
name: unit-tests-core-results
83+
path: stream-android-core/build/reports/tests/testDebugUnitTest/index.html
84+
85+
- uses: GetStream/android-ci-actions/actions/setup-ruby@main

build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import java.util.Calendar
44
apply(plugin = "io.github.gradle-nexus.publish-plugin")
55
apply(plugin = "org.jetbrains.dokka")
66

7+
apply(from = "${rootDir}/gradle/scripts/sonar.gradle")
78
// Top-level build file where you can add configuration options common to all sub-projects/modules.
89
plugins {
910
alias(libs.plugins.android.application) apply false
@@ -16,6 +17,8 @@ plugins {
1617
alias(libs.plugins.nexus) apply false
1718
alias(libs.plugins.arturbosch.detekt) apply true
1819
alias(libs.plugins.spotless) apply true
20+
alias(libs.plugins.sonarqube) apply true
21+
alias(libs.plugins.kover) apply true
1922
}
2023

2124
spotless {
@@ -37,6 +40,7 @@ detekt {
3740

3841
// License tasks
3942
subprojects {
43+
apply(from = "${rootDir}/gradle/scripts/coverage.gradle")
4044
tasks.register("generateLicense") {
4145
val currentYear = Calendar.getInstance().get(Calendar.YEAR).toString()
4246
val licenseTemplate = file("../config/license/license.template")

gradle/libs.versions.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ ksp = "2.2.0-2.0.2"
2323
robolectric = "4.15.1"
2424
detekt = "1.23.8"
2525
spotless = "7.2.1"
26-
klint = "13.0.0"
26+
kover = "0.9.1"
27+
sonarqube = "6.0.1.5171"
2728
kotlinDokka = "1.9.20"
2829
nexusPlugin = "1.3.0"
2930

@@ -75,3 +76,6 @@ arturbosch-detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt"
7576
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
7677
dokka = { id = "org.jetbrains.dokka", version.ref = "kotlinDokka" }
7778
nexus = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexusPlugin" }
79+
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover"}
80+
sonarqube = { id = "org.sonarqube", version.ref = "sonarqube"}
81+

gradle/scripts/coverage.gradle

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
if (!rootProject.ext.sonar.ignoreModules.contains(name)) {
2+
apply plugin: "org.jetbrains.kotlinx.kover"
3+
apply plugin: "org.sonarqube"
4+
5+
if (hasProperty('android')) {
6+
android {
7+
buildTypes {
8+
debug {
9+
testCoverageEnabled = true
10+
enableUnitTestCoverage = true
11+
enableAndroidTestCoverage true
12+
}
13+
}
14+
}
15+
}
16+
17+
kover {
18+
reports {
19+
verify {
20+
warningInsteadOfFailure = true
21+
}
22+
}
23+
}
24+
25+
sonarqube {
26+
properties {
27+
property "sonar.junit.reportPaths", "./build/test-results/testDebugUnitTest"
28+
property "sonar.coverage.jacoco.xmlReportPaths", "./build/reports/kover/reportDebug.xml"
29+
property "sonar.sources", "src/main/java"
30+
}
31+
}
32+
}

gradle/scripts/sonar.gradle

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
apply plugin: "org.sonarqube"
2+
3+
ext.sonar = [
4+
ignoreModules : [
5+
'stream-android-core-lint',
6+
'stream-android-core-annotations',
7+
'app'
8+
],
9+
excludeFilter : [
10+
'**/test/**',
11+
'**/androidTest/**',
12+
'**/R.class',
13+
'**/R2.class',
14+
'**/R$*.class',
15+
'**/BuildConfig.*',
16+
'**/Manifest*.*',
17+
'**/*Test*.*'
18+
]
19+
]
20+
21+
ext.sonar.ignoreModules.each {
22+
ext.sonar.excludeFilter << "**/${it}/**"
23+
}
24+
25+
sonarqube {
26+
properties {
27+
property("sonar.host.url", "https://sonarcloud.io")
28+
property("sonar.token", "${System.getenv("SONAR_TOKEN")}")
29+
property("sonar.organization", "getstream")
30+
property("sonar.projectKey", "GetStream_stream-core-android")
31+
property("sonar.projectName", "stream-core-android")
32+
property "sonar.java.coveragePlugin", "jacoco"
33+
property "sonar.sourceEncoding", "UTF-8"
34+
property "sonar.java.binaries", "${rootDir}/**/build/tmp/java-classes/debug"
35+
property "sonar.coverage.exclusions", rootProject.ext.sonar.excludeFilter
36+
}
37+
}

settings.gradle.kts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ pluginManagement {
1414
}
1515
mavenCentral()
1616
gradlePluginPortal()
17-
18-
17+
maven("https://plugins.gradle.org/m2/")
1918
}
2019
}
2120
dependencyResolutionManagement {

stream-android-core/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ plugins {
66
alias(libs.plugins.kotlin.android)
77
alias(libs.plugins.ksp)
88
alias(libs.plugins.arturbosch.detekt)
9+
alias(libs.plugins.sonarqube)
10+
alias(libs.plugins.kover)
911
}
1012

1113
rootProject.extra.apply {

stream-android-core/src/main/java/io/getstream/android/core/api/model/config/StreamHttpConfig.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package io.getstream.android.core.api.model.config
1717

18+
import io.getstream.android.core.annotations.StreamCoreApi
1819
import okhttp3.Interceptor
1920
import okhttp3.OkHttpClient
2021

@@ -25,6 +26,7 @@ import okhttp3.OkHttpClient
2526
* @param automaticInterceptors Whether to add automatic interceptors.
2627
* @param configuredInterceptors The configured interceptors.
2728
*/
29+
@StreamCoreApi
2830
data class StreamHttpConfig(
2931
val httpBuilder: OkHttpClient.Builder,
3032
val automaticInterceptors: Boolean = true,

stream-android-core/src/main/java/io/getstream/android/core/internal/subscribe/StreamSubscriptionManagerImpl.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ internal class StreamSubscriptionManagerImpl<T>(
117117
object : StreamSubscription {
118118
override fun cancel() {
119119
val key = keyRef.get() ?: return
120-
synchronized(weakSubscribers) { weakSubscribers.remove(key, this) }
120+
synchronized(weakSubscribers) { weakSubscribers.remove(key) }
121121
}
122122
}
123123
weakSubscribers[listener] = handle

stream-android-core/src/test/java/io/getstream/android/core/internal/processing/StreamSingleFlightProcessorImplTest.kt

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,16 @@ import io.mockk.coVerify
2222
import io.mockk.mockk
2323
import java.util.concurrent.ConcurrentHashMap
2424
import java.util.concurrent.ConcurrentMap
25+
import java.util.concurrent.Executors
2526
import kotlin.test.assertFailsWith
2627
import kotlin.test.assertFalse
2728
import kotlinx.coroutines.CancellationException
2829
import kotlinx.coroutines.CompletableDeferred
2930
import kotlinx.coroutines.CoroutineScope
3031
import kotlinx.coroutines.Deferred
31-
import kotlinx.coroutines.Dispatchers
3232
import kotlinx.coroutines.ExperimentalCoroutinesApi
3333
import kotlinx.coroutines.SupervisorJob
34+
import kotlinx.coroutines.asCoroutineDispatcher
3435
import kotlinx.coroutines.async
3536
import kotlinx.coroutines.channels.ClosedSendChannelException
3637
import kotlinx.coroutines.delay
@@ -418,41 +419,46 @@ class StreamSingleFlightProcessorImplTest {
418419

419420
@Test
420421
fun `run with racing callers where loser joins existing flight`() = runTest {
421-
val map = RecordingMap<Any, Deferred<Result<*>>>()
422-
val sf =
423-
StreamSingleFlightProcessorImpl(
424-
scope = CoroutineScope(SupervisorJob() + Dispatchers.Default),
425-
flights = map,
426-
)
427-
val key = "k".asStreamTypedKey<Int>()
428-
429-
repeat(30) {
430-
val gate = java.util.concurrent.CountDownLatch(1)
431-
val a =
432-
async(Dispatchers.Default) {
433-
gate.await()
434-
sf.run(key) {
435-
delay(10)
436-
1
422+
val pool = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
423+
try {
424+
val map = RecordingMap<Any, Deferred<Result<*>>>()
425+
val sf =
426+
StreamSingleFlightProcessorImpl(
427+
scope = CoroutineScope(SupervisorJob() + pool),
428+
flights = map,
429+
)
430+
val key = "k".asStreamTypedKey<Int>()
431+
432+
repeat(200) { // more attempts to guarantee at least one race
433+
val gate = java.util.concurrent.CountDownLatch(1)
434+
val a =
435+
async(pool) {
436+
gate.await()
437+
sf.run(key) {
438+
delay(100) // keep the winner running long enough
439+
1
440+
}
437441
}
438-
}
439-
val b =
440-
async(Dispatchers.Default) {
441-
gate.await()
442-
sf.run(key) {
443-
delay(10)
444-
1
442+
val b =
443+
async(pool) {
444+
gate.await()
445+
sf.run(key) {
446+
delay(100)
447+
1
448+
}
445449
}
446-
}
447-
gate.countDown()
448-
a.await()
449-
b.await()
450-
}
450+
gate.countDown()
451+
a.await()
452+
b.await()
453+
}
451454

452-
assertTrue(
453-
"Expected putIfAbsent to return existing at least once",
454-
map.installedNonNull.get(),
455-
)
455+
assertTrue(
456+
"Expected putIfAbsent to return existing at least once",
457+
map.installedNonNull.get(),
458+
)
459+
} finally {
460+
pool.close() // or pool.executor.shutdown()
461+
}
456462
}
457463

458464
@Test

0 commit comments

Comments
 (0)