Skip to content
Open
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
5 changes: 1 addition & 4 deletions android-snaptesting/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin)
}

android {
Expand Down Expand Up @@ -31,13 +30,11 @@ android {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}

kotlin {
explicitApi()
jvmToolchain(17)
}

dependencies {
Expand Down
8 changes: 4 additions & 4 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin)
id("com.telefonica.androidsnaptesting-plugin")
}

Expand Down Expand Up @@ -31,9 +30,10 @@ android {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}

kotlin {
jvmToolchain(17)
}

dependencies {
Expand Down
5 changes: 1 addition & 4 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,4 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
# Non-transitive R classes are the default in AGP 9.x and above
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[versions]
agp = "8.13.1"
agp = "9.1.0"
constraintlayout = "2.2.1"
min-sdk = "23"
target-sdk = "36"
compile-sdk = "36"
material = "1.12.0"
kotlin = "2.1.21"
kotlin = "2.3.0"
appcompat = "1.7.0"
androidx-junit = "1.2.1"
androidx-monitor = "1.7.2"
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 @@
#Fri Mar 22 10:54:28 CET 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
package com.telefonica.androidsnaptesting

import com.android.build.gradle.TestedExtension
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import java.io.File

class AndroidSnaptestingPlugin : Plugin<Project> {

override fun apply(project: Project) {
// Collect applicationId per test-variant name at configuration time using the new variant API.
// onVariants runs during project configuration, before afterEvaluate.
val applicationIds = mutableMapOf<String, Provider<String>>()

project.extensions.findByType(ApplicationAndroidComponentsExtension::class.java)
?.onVariants { variant ->
// variant.name == "debug" β†’ test task variant name == "debugAndroidTest"
applicationIds["${variant.name}AndroidTest"] = variant.applicationId
}

project.afterEvaluate {

val deviceProviderInstrumentTestTasks = project.tasks
Expand All @@ -21,8 +33,8 @@ class AndroidSnaptestingPlugin : Plugin<Project> {
throw AndroidSnaptestingNoDeviceProviderInstrumentTestTasksException()
}

val extension = project.extensions.findByType(TestedExtension::class.java)
?: throw RuntimeException("TestedExtension not found")
val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class.java)
?: throw RuntimeException("AndroidComponentsExtension not found")

val isRecordMode = project.properties["android.testInstrumentationRunnerArguments.record"] == "true"
val providerFactory: ProviderFactory = project.providers
Expand All @@ -32,7 +44,18 @@ class AndroidSnaptestingPlugin : Plugin<Project> {
taskName,
DeviceProviderInstrumentTestTask::class.java,
).get()
registerTasksForVariant(project, taskName, deviceProviderTask, extension, isRecordMode, providerFactory)
val variantName = deviceProviderTask.variantName
val applicationIdProvider = applicationIds[variantName]
?: throw RuntimeException(
"applicationId not found for test variant '$variantName'. " +
"Available variants: ${applicationIds.keys}. " +
"Make sure the plugin is applied to a com.android.application module."
)
registerTasksForVariant(
project, taskName, deviceProviderTask,
androidComponents, applicationIdProvider,
isRecordMode, providerFactory,
)
}
}
}
Expand All @@ -42,17 +65,13 @@ class AndroidSnaptestingPlugin : Plugin<Project> {
project: Project,
taskName: String,
deviceProviderTask: DeviceProviderInstrumentTestTask,
extension: TestedExtension,
androidComponents: AndroidComponentsExtension<*, *, *>,
applicationIdProvider: Provider<String>,
isRecordMode: Boolean,
providerFactory: ProviderFactory,
) {
val capitalizedVariant = deviceProviderTask.variantName.capitalizeFirstLetter()

val testedVariant = extension.testVariants
.firstOrNull { it.name == deviceProviderTask.variantName }
?: throw RuntimeException("TestVariant not found for ${deviceProviderTask.variantName}")
val applicationIdProvider = providerFactory.provider { testedVariant.applicationId }
val adbExecutablePath = extension.adbExecutable.absolutePath
val adbExecutablePath = androidComponents.sdkComponents.adb.get().asFile.absolutePath

val goldenSnapshotsSourcePath = run {
val variantSourceFolder = deviceProviderTask
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ package com.telefonica.androidsnaptesting

import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask
import com.android.build.gradle.internal.testing.ConnectedDevice
import com.android.ddmlib.CollectingOutputReceiver
import com.android.ddmlib.FileListingService
import com.android.ddmlib.FileListingService.FileEntry
import com.android.ddmlib.IDevice
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.ProviderFactory
import java.io.File
import java.util.concurrent.TimeUnit

fun DeviceProviderInstrumentTestTask.deviceFileManager(
applicationId: String,
Expand All @@ -23,39 +20,25 @@ class DeviceFileManager(
private val providerFactory: ProviderFactory,
) {

fun pullRecordedSnapshots(
destinationPath: String,
) {
fun pullRecordedSnapshots(destinationPath: String) {
pullSnapshots("recorded", destinationPath)
}

fun pullFailuresSnapshots(
destinationPath: String,
) {
fun pullFailuresSnapshots(destinationPath: String) {
pullSnapshots("failures", destinationPath)
}

fun clearAllSnapshots() {
withConnectedDevices { devices ->
devices.forEach {
val receiver = CollectingOutputReceiver()
it.iDevice.executeShellCommand("rm -rf ${getDeviceAndroidSnaptestingRootAbsolutePath()}", receiver)
println(receiver.output)
devices.forEach { device ->
runAdb(device.serialNumber, "shell", "rm", "-rf", getDeviceAndroidSnaptestingRootAbsolutePath())
}
}
}

private fun String.toFileEntry(): FileEntry {
val parts = this.split("/")
var fileEntry = FileEntry(null, null, FileListingService.TYPE_DIRECTORY, true)
parts.forEach {
fileEntry = FileEntry(fileEntry, it, FileListingService.TYPE_DIRECTORY, false)
}
return fileEntry
}

private fun getDeviceAndroidSnaptestingRootAbsolutePath(): String =
"${FileListingService.DIRECTORY_SDCARD}/Download/android-snaptesting/$applicationId"
"/sdcard/Download/android-snaptesting/$applicationId"

private fun getDeviceAndroidSnaptestingSubfolderAbsolutePath(subFolder: String): String =
"${getDeviceAndroidSnaptestingRootAbsolutePath()}/$subFolder"

Expand All @@ -77,25 +60,40 @@ class DeviceFileManager(
androidSnaptestingSubFolderInDevice: String,
destinationPath: String,
) {
val fileEntry = getDeviceAndroidSnaptestingSubfolderAbsolutePath(androidSnaptestingSubFolderInDevice).toFileEntry()
val remotePath = getDeviceAndroidSnaptestingSubfolderAbsolutePath(androidSnaptestingSubFolderInDevice)
withConnectedDevices { devices ->
devices.forEach {
pullFolderFiles(
fileEntry,
it.iDevice,
destinationPath,
)
devices.forEach { device ->
val serial = device.serialNumber
// List files in the remote folder; ignore errors if the folder doesn't exist yet
val lsOutput = runAdbCapture(serial, "shell", "ls", remotePath)
val fileNames = lsOutput.lines()
.map { it.trim() }
.filter { it.isNotBlank() && !it.startsWith("ls:") && !it.contains("No such file") }
// Pull each file to the local destination
fileNames.forEach { fileName ->
runAdb(serial, "pull", "$remotePath/$fileName", "$destinationPath/$fileName")
}
}
}
}

private fun pullFolderFiles(
androidSnaptestingDeviceFolder: FileEntry,
device: IDevice,
destinationPath: String,
) {
device.fileListingService.getChildrenSync(androidSnaptestingDeviceFolder).forEach {
device.pullFile(it.fullPath, "$destinationPath/${it.name}")
private fun runAdb(serial: String, vararg args: String) {
val output = runAdbCapture(serial, *args)
println(output)
}

private fun runAdbCapture(serial: String, vararg args: String): String {
val command = buildList {
add(adbExecutablePath)
add("-s")
add(serial)
addAll(args.toList())
}
val process = ProcessBuilder(command)
.redirectErrorStream(true)
.start()
val output = process.inputStream.bufferedReader().readText()
process.waitFor(60, TimeUnit.SECONDS)
return output
}
}
8 changes: 4 additions & 4 deletions include-build/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[versions]
agp = "8.4.1"
common = "31.4.1"
ddmlib = "31.4.1"
kotlin = "1.9.23"
agp = "9.1.0"
common = "32.1.0"
ddmlib = "32.1.0"
kotlin = "2.3.0"
detekt = "1.23.6"
publish-plugin = "1.2.0"

Expand Down
2 changes: 1 addition & 1 deletion mavencentral.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ publishing {
artifactId 'androidsnaptesting'
version version

artifact("$buildDir/outputs/aar/android-snaptesting-release.aar")
artifact("${layout.buildDirectory.get().asFile}/outputs/aar/android-snaptesting-release.aar")
artifact androidSourcesJar

pom {
Expand Down
Loading