Skip to content
Draft
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
21 changes: 14 additions & 7 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name: Build and Test
on:
push:
branches: [ main, beta ]
branches: [ main ]
pull_request:
branches: [ main, beta ]
branches: [ main ]

jobs:
build:
Expand Down Expand Up @@ -44,19 +44,26 @@ jobs:
restore-keys: ${{ runner.os }}-sonar

- name: Build and analyze
if: ${{ github.secret_source != 'Actions' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
run: |
echo "Secret source: \"${{ github.secret_source }}\""
./gradlew build jacocoTestReport
./gradlew build testDebugUnitTest

- name: Build and analyze (Sonar)
- name: Run Android Instrumented Tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 35
target: default
arch: x86_64
profile: Nexus 6
script: ./gradlew connectedAndroidTest jacocoCombinedReport

- name: Run Sonar
if: ${{ github.secret_source == 'Actions' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew build jacocoTestReport sonar
run: ./gradlew sonar

- name: Upload build artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
Expand Down
55 changes: 54 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,65 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) {

// Make sure the path is correct (if not run the unit tests and try find the .exec file that is generated after the unit tests are finished should be similar to that one)
executionData.from = fileTree(dir: "$buildDir", includes: ["outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec"])
}

task jacocoCombinedReport(type: JacocoReport, dependsOn: ['testDebugUnitTest','connectedDebugAndroidTest']) {
// Ensure this task runs after unit and instrumented tests have completed
// dependsOn tasks.named("testDebugUnitTest")
// dependsOn tasks.named("connectedDebugAndroidTest") // Or the specific task like 'createDebugCoverageReport'

group = "Reporting"
description = "Generates a combined JaCoCo code coverage report for unit and instrumented tests."

reports {
xml.required = true
html.required = false
csv.required = false
}

// Paths to execution data files
def unitTestExecFile = "$buildDir/jacoco/testDebugUnitTest.exec"
// For AGP 7.x+, instrumented test .ec files are often here:
def instrumentedTestExecFiles = fileTree(dir: "$buildDir/outputs/code_coverage/debugAndroidTest/connected", includes: ["**/*.ec"])
// For older AGP versions, the path might differ, e.g., "$buildDir/outputs/androidTest-results/connected/coverage.ec"

executionData.from = files(unitTestExecFile, instrumentedTestExecFiles)

// Source directories (adjust if your structure is different)
sourceDirectories.from = files(
"$projectDir/src/main/java",
"$projectDir/src/main/kotlin" // Add if you use Kotlin
)

// Class directories (compiled .class files)
// This is the most complex part and highly dependent on your AGP version and project setup.
// The goal is to point to the compiled classes of your 'main' source set for the 'debug' variant.
// Exclude Android generated classes, Dagger/Hilt, DataBinding, etc.
def mainClasses = fileTree(dir: "$buildDir/tmp/kotlin-classes/debug", // Common for Kotlin projects
excludes: [
'**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*',
'**/*_MembersInjector.class', '**/*_Factory.class', '**/*_Provide*Factory.class', // Dagger/Hilt
'**/*Args.class', '**/*Args$*.class', '**/*Directions.class', '**/*Directions$*.class', // Navigation Components
'**/*Binding.class', '**/BR.class', // Data Binding
'**/*JsonAdapter.class' // Moshi or similar
// Add more exclusions as needed for your project
])
// If you also have Java sources, you might need to add paths like:
// def javaMainClasses = fileTree(dir: "$buildDir/intermediates/javac/debug/classes", excludes: [...])
// classDirectories.from = files(mainClasses, javaMainClasses)
classDirectories.from = files(mainClasses)


// Output location for the combined report
reports.html.outputLocation = file("$buildDir/reports/jacoco/combined/html")
reports.xml.outputLocation = file("$buildDir/reports/jacoco/combined/report.xml") // Common for CI/Sonar
reports.csv.outputLocation = file("$buildDir/reports/jacoco/combined/report.csv")
}


sonar {
properties {
property "sonar.junit.reportPaths", "build/test-results/testDebugUnitTest/"
property "sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml"
property "sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/combined/report.xml"
}
}
30 changes: 15 additions & 15 deletions app/src/androidTest/java/org/blitzortung/android/app/MainTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
Expand All @@ -27,7 +26,7 @@ class MainTest {
val activityRule = ActivityScenarioRule(Main::class.java)

private lateinit var uiDevice: androidx.test.uiautomator.UiDevice
private val timeout = 5000L // 5 seconds
private val timeout = 1000L // 5 seconds

@Before
fun setUp() {
Expand Down Expand Up @@ -59,8 +58,14 @@ class MainTest {
)?.click()

uiDevice.wait(
Until.hasObject(By.res(InstrumentationRegistry.getInstrumentation().targetContext.packageName, "activity_settings")),
timeout)
Until.hasObject(
By.res(
InstrumentationRegistry.getInstrumentation().targetContext.packageName,
"activity_settings"
)
),
timeout
)

// Check if the settings fragment is displayed
onView(withId(R.id.activity_settings)).check(matches(isDisplayed()))
Expand Down Expand Up @@ -88,17 +93,12 @@ class MainTest {
timeout
)

// Verify that the button was found
Assert.assertNotNull(
"Location permission dialog with 'Allow' button not found within ${timeout}ms. " +
"Permissions might have been already granted or the dialog has different text.",
allowButton
)

// Click the "allow" button
allowButton.click()
if (allowButton != null) {
// Click the "allow" button
allowButton.click()

// Optional: Wait for the dialog to disappear to ensure the action is processed.
uiDevice.wait(Until.gone(By.text(allowButtonTextPattern)), 2000L)
// Optional: Wait for the dialog to disappear to ensure the action is processed.
uiDevice.wait(Until.gone(By.text(allowButtonTextPattern)), 2000L)
}
}
}
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
Loading