Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Android CI

on:
push:
branches:
- develop
- main

pull_request:
branches:
- '**'

workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
BUILD_CACHE_AWS_REGION: ${{ secrets.BUILD_CACHE_AWS_REGION }}
BUILD_CACHE_AWS_BUCKET: ${{ secrets.BUILD_CACHE_AWS_BUCKET }}
BUILD_CACHE_AWS_ACCESS_KEY_ID: ${{ secrets.BUILD_CACHE_AWS_ACCESS_KEY_ID }}
BUILD_CACHE_AWS_SECRET_KEY: ${{ secrets.BUILD_CACHE_AWS_SECRET_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_PR_NUM: ${{ github.event.pull_request.number }}

jobs:
build:
name: Compile
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- uses: GetStream/android-ci-actions/actions/setup-java@main

- uses: GetStream/android-ci-actions/actions/gradle-cache@main

- name: Make Gradle executable
run: chmod +x ./gradlew

- name: Build with Gradle
run: ./gradlew assembleDebug --scan

spotless:
name: Spotless
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/[email protected]
- uses: GetStream/android-ci-actions/actions/setup-java@main
- name: spotless
run: ./gradlew spotlessCheck --scan
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/[email protected]
- uses: GetStream/android-ci-actions/actions/setup-java@main
- name: spotless
run: ./gradlew lint

unitTest:
name: Unit Tests
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v3

- uses: GetStream/android-ci-actions/actions/setup-java@main

- uses: GetStream/android-ci-actions/actions/gradle-cache@main

- name: Run unit tests
run: ./gradlew :stream-android-core:koverXmlReportDebug --scan --stacktrace

- name: Unit tests core results
uses: actions/upload-artifact@v4
if: failure()
with:
name: unit-tests-core-results
path: stream-android-core/build/reports/tests/testDebugUnitTest/index.html

- uses: GetStream/android-ci-actions/actions/setup-ruby@main
5 changes: 4 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import java.io.FileNotFoundException
import java.util.Calendar

apply(from = "${rootDir}/gradle/scripts/sonar.gradle")
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
Expand All @@ -11,6 +11,8 @@ plugins {
alias(libs.plugins.ksp) apply false
alias(libs.plugins.arturbosch.detekt) apply true
alias(libs.plugins.spotless) apply true
alias(libs.plugins.sonarqube) apply true
alias(libs.plugins.kover) apply true
}

spotless {
Expand All @@ -32,6 +34,7 @@ detekt {

// License tasks
subprojects {
apply(from = "${rootDir}/gradle/scripts/coverage.gradle")
tasks.register("generateLicense") {
val currentYear = Calendar.getInstance().get(Calendar.YEAR).toString()
val licenseTemplate = file("../config/license/license.template")
Expand Down
5 changes: 4 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ ksp = "2.2.0-2.0.2"
robolectric = "4.15.1"
detekt = "1.23.8"
spotless = "7.2.1"
klint = "13.0.0"
kover = "0.9.1"
sonarqube = "6.0.1.5171"


[libraries]
Expand Down Expand Up @@ -72,4 +73,6 @@ kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "ko
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
arturbosch-detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover"}
sonarqube = { id = "org.sonarqube", version.ref = "sonarqube"}

32 changes: 32 additions & 0 deletions gradle/scripts/coverage.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
if (!rootProject.ext.sonar.ignoreModules.contains(name)) {
apply plugin: "org.jetbrains.kotlinx.kover"
apply plugin: "org.sonarqube"

if (hasProperty('android')) {
android {
buildTypes {
debug {
testCoverageEnabled = true
enableUnitTestCoverage = true
enableAndroidTestCoverage true
}
}
}
}

kover {
reports {
verify {
warningInsteadOfFailure = true
}
}
}

sonarqube {
properties {
property "sonar.junit.reportPaths", "./build/test-results/testDebugUnitTest"
property "sonar.coverage.jacoco.xmlReportPaths", "./build/reports/kover/reportDebug.xml"
property "sonar.sources", "src/main/java"
}
}
}
37 changes: 37 additions & 0 deletions gradle/scripts/sonar.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
apply plugin: "org.sonarqube"

ext.sonar = [
ignoreModules : [
'stream-android-core-lint',
'stream-android-core-annotations',
'app'
],
excludeFilter : [
'**/test/**',
'**/androidTest/**',
'**/R.class',
'**/R2.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*'
]
]

ext.sonar.ignoreModules.each {
ext.sonar.excludeFilter << "**/${it}/**"
}

sonarqube {
properties {
property("sonar.host.url", "https://sonarcloud.io")
property("sonar.token", "${System.getenv("SONAR_TOKEN")}")
property("sonar.organization", "getstream")
property("sonar.projectKey", "GetStream_stream-core-android")
property("sonar.projectName", "stream-core-android")
property "sonar.java.coveragePlugin", "jacoco"
property "sonar.sourceEncoding", "UTF-8"
property "sonar.java.binaries", "${rootDir}/**/build/tmp/java-classes/debug"
property "sonar.coverage.exclusions", rootProject.ext.sonar.excludeFilter
}
}
3 changes: 1 addition & 2 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ pluginManagement {
}
mavenCentral()
gradlePluginPortal()


maven("https://plugins.gradle.org/m2/")
}
}
dependencyResolutionManagement {
Expand Down
2 changes: 2 additions & 0 deletions stream-android-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ plugins {
alias(libs.plugins.kotlin.android)
alias(libs.plugins.ksp)
alias(libs.plugins.arturbosch.detekt)
alias(libs.plugins.sonarqube)
alias(libs.plugins.kover)
}

kotlin {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.getstream.android.core.api.model.config

import io.getstream.android.core.annotations.StreamCoreApi
import okhttp3.Interceptor
import okhttp3.OkHttpClient

Expand All @@ -25,6 +26,7 @@ import okhttp3.OkHttpClient
* @param automaticInterceptors Whether to add automatic interceptors.
* @param configuredInterceptors The configured interceptors.
*/
@StreamCoreApi
data class StreamHttpConfig(
val httpBuilder: OkHttpClient.Builder,
val automaticInterceptors: Boolean = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ internal class StreamSubscriptionManagerImpl<T>(
object : StreamSubscription {
override fun cancel() {
val key = keyRef.get() ?: return
synchronized(weakSubscribers) { weakSubscribers.remove(key, this) }
synchronized(weakSubscribers) { weakSubscribers.remove(key) }
}
}
weakSubscribers[listener] = handle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ import io.mockk.coVerify
import io.mockk.mockk
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
import java.util.concurrent.Executors
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.async
import kotlinx.coroutines.channels.ClosedSendChannelException
import kotlinx.coroutines.delay
Expand Down Expand Up @@ -418,41 +419,46 @@ class StreamSingleFlightProcessorImplTest {

@Test
fun `run with racing callers where loser joins existing flight`() = runTest {
val map = RecordingMap<Any, Deferred<Result<*>>>()
val sf =
StreamSingleFlightProcessorImpl(
scope = CoroutineScope(SupervisorJob() + Dispatchers.Default),
flights = map,
)
val key = "k".asStreamTypedKey<Int>()

repeat(30) {
val gate = java.util.concurrent.CountDownLatch(1)
val a =
async(Dispatchers.Default) {
gate.await()
sf.run(key) {
delay(10)
1
val pool = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
try {
val map = RecordingMap<Any, Deferred<Result<*>>>()
val sf =
StreamSingleFlightProcessorImpl(
scope = CoroutineScope(SupervisorJob() + pool),
flights = map,
)
val key = "k".asStreamTypedKey<Int>()

repeat(200) { // more attempts to guarantee at least one race
val gate = java.util.concurrent.CountDownLatch(1)
val a =
async(pool) {
gate.await()
sf.run(key) {
delay(100) // keep the winner running long enough
1
}
}
}
val b =
async(Dispatchers.Default) {
gate.await()
sf.run(key) {
delay(10)
1
val b =
async(pool) {
gate.await()
sf.run(key) {
delay(100)
1
}
}
}
gate.countDown()
a.await()
b.await()
}
gate.countDown()
a.await()
b.await()
}

assertTrue(
"Expected putIfAbsent to return existing at least once",
map.installedNonNull.get(),
)
assertTrue(
"Expected putIfAbsent to return existing at least once",
map.installedNonNull.get(),
)
} finally {
pool.close() // or pool.executor.shutdown()
}
}

@Test
Expand Down