Skip to content

Commit

Permalink
Setup Android Tests CI Setup (#48)
Browse files Browse the repository at this point in the history
# Setup Android Tests CI Setup

## ♻️ Current situation & Problem
- Addresses #16
- Closes #21

## ⚙️ Release Notes 
- Setup Android Tests CI Setup

## ☑️ Remaining Issues
- [x] Report Code Coverage: I have build the rough setup for running the
tests but the code coverage report currently doesn't work as expected
for the Android tests. Codecov seems to merge reports for the same
commit so we can just upload them all in different builds. Would be
amazing if you can take a look at this @eldcn
- [x] Contact Tests Fail: It seems like the contact Android tests fail
locally and on the CI. Good as a test for the setup, would be great if
we can fix them as part of this PR @Basler182

## 📝 Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md).

---------

Co-authored-by: Eldi Cano <[email protected]>
  • Loading branch information
PSchmiedmayer and eldcn authored Jun 26, 2024
1 parent 19b0573 commit ec45f58
Show file tree
Hide file tree
Showing 14 changed files with 149 additions and 42 deletions.
71 changes: 70 additions & 1 deletion .github/workflows/build-test-analyze.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3.13.0
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
Expand All @@ -69,6 +69,75 @@ jobs:
flags: unittests
name: codecov-coverage
token: ${{ secrets.CODECOV_TOKEN }}
test:
name: Instrumented tests
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
api-level: [31, 34]
profile: ["pixel_6"]
target: ["default", "google_apis"]
steps:
- name: checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
cache: gradle
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Gradle cache
uses: gradle/actions/setup-gradle@v3
- name: AVD cache
uses: actions/cache@v4
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-${{ matrix.api-level }}-${{ matrix.target }}-${{ matrix.profile }}
- name: Create AVD and Generate Snapshot for Caching
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
profile: ${{ matrix.profile }}
arch: x86_64
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: false
script: |
sdkmanager --list
avdmanager list devices
- name: Run Tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
profile: ${{ matrix.profile }}
arch: x86_64
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: bundle exec fastlane connectedCheck
- name: Upload JaCoCo report to Codecov
uses: codecov/codecov-action@v4
with:
files: '**/build/reports/coverage/androidTest/debug/connected/index.html'
flags: uitests
name: codecov-coverage
token: ${{ secrets.CODECOV_TOKEN }}
dokka:
name: Dokka Documentation Deployment
runs-on: ubuntu-latest
Expand Down
26 changes: 13 additions & 13 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@ GEM
base64
nkf
rexml
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.941.0)
aws-sdk-core (3.197.0)
aws-partitions (1.947.0)
aws-sdk-core (3.199.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.83.0)
aws-sdk-core (~> 3, >= 3.197.0)
aws-sdk-kms (1.87.0)
aws-sdk-core (~> 3, >= 3.199.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.152.0)
aws-sdk-core (~> 3, >= 3.197.0)
aws-sdk-s3 (1.154.0)
aws-sdk-core (~> 3, >= 3.199.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
aws-sigv4 (1.8.0)
Expand Down Expand Up @@ -68,7 +68,7 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.3.1)
fastlane (2.220.0)
fastlane (2.221.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
Expand Down Expand Up @@ -152,9 +152,9 @@ GEM
httpclient (2.8.3)
jmespath (1.6.2)
json (2.7.2)
jwt (2.8.1)
jwt (2.8.2)
base64
mini_magick (4.12.0)
mini_magick (4.13.1)
mini_mime (1.1.5)
multi_json (1.15.0)
multipart-post (2.4.1)
Expand All @@ -164,7 +164,7 @@ GEM
optparse (0.5.0)
os (1.1.4)
plist (3.7.1)
public_suffix (5.0.5)
public_suffix (6.0.0)
rake (13.2.1)
representable (3.2.0)
declarative (< 0.1.0)
Expand Down Expand Up @@ -217,4 +217,4 @@ DEPENDENCIES
fastlane

BUNDLED WITH
2.5.9
2.5.11
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class SpeziBaseConfigConventionPlugin : Plugin<Project> {
targetCompatibility = java
}

buildTypes {
getByName("debug").enableAndroidTestCoverage = true
}

packaging {
resources {
excludes += "/META-INF/**.md"
Expand Down
9 changes: 7 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,14 @@ fun Project.setupJacoco() {
html.required.set(true)
xml.required.set(true)
}

sourceDirectories.setFrom(files("$projectDir/src/main"))
executionData.setFrom(files("$buildDir/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec"))

executionData.setFrom(
files("$buildDir/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec")
)
doLast {
println("Jacoco report generated in: ${reports.html.outputLocation.get()}")
}
}

tasks.withType<Test>().configureEach {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ internal class BLEDeviceScanner @Inject constructor(
* If scanning is already in progress, this method does nothing.
*/
fun startScanning() {
if (_isScanning.getAndSet(true)) return
val scanner = bluetoothAdapter.bluetoothLeScanner
if (_isScanning.getAndSet(scanner != null)) return
val filters = supportedServices.map {
ScanFilter.Builder()
.setServiceUuid(ParcelUuid(it.service))
Expand All @@ -79,7 +80,7 @@ internal class BLEDeviceScanner @Inject constructor(
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
bluetoothAdapter.bluetoothLeScanner.startScan(filters, settings, scanCallback)
scanner?.startScan(filters, settings, scanCallback)
}

/**
Expand All @@ -89,7 +90,7 @@ internal class BLEDeviceScanner @Inject constructor(
*/
fun stopScanning() {
if (_isScanning.getAndSet(false).not()) return
bluetoothAdapter.bluetoothLeScanner.stopScan(scanCallback)
bluetoothAdapter.bluetoothLeScanner?.stopScan(scanCallback)
}

private fun emit(event: Event) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import edu.stanford.spezi.core.bluetooth.data.model.BLEServiceType
import edu.stanford.spezi.core.bluetooth.data.model.SupportedServices
import edu.stanford.spezi.core.testing.SpeziTestScope
import edu.stanford.spezi.core.testing.runTestUnconfined
import edu.stanford.spezi.core.testing.verifyNever
import io.mockk.Called
import io.mockk.Runs
import io.mockk.every
Expand Down Expand Up @@ -86,6 +87,19 @@ class BLEDeviceScannerTest {
assertThat(bleDeviceScanner.isScanning).isTrue()
}

@Test
fun `it should not start scanning if le scanner is not available`() {
// given
every { bluetoothAdapter.bluetoothLeScanner } returns null

// when
bleDeviceScanner.startScanning()

// then
assertThat(bleDeviceScanner.isScanning).isFalse()
verifyNever { bluetoothLeScanner.startScan(any()) }
}

@Test
fun `it should start scanning only once if already scanning`() {
// given
Expand Down Expand Up @@ -129,6 +143,22 @@ class BLEDeviceScannerTest {
assertThat(bleDeviceScanner.isScanning).isFalse()
}

@Test
fun `it should safely stop scanning`() {
// given
val callback = getCallback()
val startedScanning = bleDeviceScanner.isScanning
every { bluetoothAdapter.bluetoothLeScanner } returns null

// when
bleDeviceScanner.stopScanning()

// then
assertThat(startedScanning).isTrue()
verifyNever { bluetoothLeScanner.stopScan(callback) }
assertThat(bleDeviceScanner.isScanning).isFalse()
}

@Test
fun `it should handle onScanResult correctly`() = runTestUnconfined {
// given
Expand Down
7 changes: 6 additions & 1 deletion fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@
default_platform(:android)

platform :android do
desc "Runs all the tests"
desc "Runs all unit tests"
lane :test do
gradle(task: "test")
end

desc "Runs all UI tests"
lane :connectedCheck do
gradle(task: "connectedCheck")
end

desc "Deploy a new version to the Google Play (Internal)"
lane :internal do
version_codes = google_play_track_version_codes(
Expand Down
12 changes: 6 additions & 6 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
[versions]
accompanistPager = "0.35.1-alpha"
activityCompose = "1.9.0"
agp = "8.4.1"
androidTools = "31.4.2"
agp = "8.5.0"
androidTools = "31.5.0"
appcompat = "1.7.0"
compileSdk = "34"
composeBom = "2024.05.00"
composeNavigation = "2.8.0-alpha08"
composeBom = "2024.06.00"
composeNavigation = "2.8.0-beta03"
coreKtx = "1.13.1"
coreKtxVersion = "1.5.0"
coreTestingVersion = "2.2.0"
Expand All @@ -21,7 +21,7 @@ firebaseAuthKtx = "23.0.0"
firebaseFirestoreKtx = "25.0.0"
firebaseFunctionsKtx = "21.0.0"
firebaseStorageKtx = "21.0.0"
foundation = "1.6.7"
foundation = "1.6.8"
googleGmsGoogleServices = "4.4.2"
googleid = "1.1.0"
hapiFhirVersion = "5.7.9"
Expand All @@ -33,7 +33,7 @@ junitVersion = "1.1.5"
kotlin = "2.0.0"
kotlinxSerializationJson = "1.6.3"
kspVersion = "2.0.0-1.0.21"
lifecycleKtx = "2.8.1"
lifecycleKtx = "2.8.2"
lifecycleRuntimeKtx = "2.7.0"
minSdk = "31"
mockKVersion = "1.13.10"
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Thu Apr 18 21:37:46 CEST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Call
import androidx.compose.material.icons.filled.Email
import androidx.compose.material.icons.filled.Info
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import edu.stanford.spezi.modules.contact.model.ContactOption
import edu.stanford.spezi.modules.contact.model.ContactOptionType
Expand All @@ -20,7 +20,7 @@ class ContactScreenTest {
private val mockContactRepository: ContactRepository = mockk()

@get:Rule
val composeTestRule = createAndroidComposeRule<TestActivity>()
val composeTestRule = createComposeRule()

@Test
fun contactView_displaysContactName() {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class EncryptedFileKeyValueStorageTest {
fileStorage.saveFile(fileName, data)

// Then
val readData = fileStorage.readFile(fileName)
val readData = fileStorage.readFile(fileName).getOrNull()
assertThat(readData).isEqualTo(data)
}

Expand All @@ -45,7 +45,7 @@ class EncryptedFileKeyValueStorageTest {
val fileName = "nonExistentFile"

// When
val readData = fileStorage.readFile(fileName)
val readData = fileStorage.readFile(fileName).getOrNull()

// Then
assertThat(readData).isNull()
Expand All @@ -62,7 +62,7 @@ class EncryptedFileKeyValueStorageTest {
fileStorage.saveFile(fileName, newData)

// Then
val readData = fileStorage.readFile(fileName)
val readData = fileStorage.readFile(fileName).getOrNull()
assertThat(readData).isEqualTo(newData)
}

Expand All @@ -76,7 +76,7 @@ class EncryptedFileKeyValueStorageTest {
fileStorage.deleteFile(fileName)

// Then
val readData = fileStorage.readFile(fileName)
val readData = fileStorage.readFile(fileName).getOrNull()
assertThat(readData).isNull()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import edu.stanford.spezi.core.testing.runTestUnconfined
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.Test

class EncryptedSharedPreferencesKeyValueStorageTest {
Expand Down Expand Up @@ -163,7 +162,7 @@ class EncryptedSharedPreferencesKeyValueStorageTest {
storage.saveData(key, expectedValue)

// When
val actualValue = runBlocking { storage.readData(key).first() }
val actualValue = storage.readData(key).first()

// Then
assertThat(actualValue).isEqualTo(expectedValue)
Expand Down
Loading

0 comments on commit ec45f58

Please sign in to comment.