Port FlutterTask from Groovy to Kotlin (#165244)

1. Ports `FlutterTask` from groovy to kotlin
2. Refactors `BaseFlutterTask` a bit, to follow a paradigm that methods
on `BaseFlutterTask` and `FlutterTask` will contain no logic, and
instead be set equal to a method with the same name in
`BaseFlutterTaskHelper` and `FlutterTaskHelper` respectively, and that
both of those helpers will be stateless. This allows us to somewhat get
around the fact that you cannot create a `Task` instance in testing,
because testing the logic of the task is the same as testing the logic
of the helper.
3. Also creates a `FlutterPluginConstants` object to hold the constants
from `FlutterPlugin`. I think ideally this will be collapsed back in to
`FlutterPlugin` when the conversion is completed, but it needs to be
moved for now so it can be referenced in Kotlin code (this pr needs it).

Fixes https://github.com/flutter/flutter/issues/162112

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Co-authored-by: Gray Mackall <mackall@google.com>
This commit is contained in:
Gray Mackall 2025-03-18 16:14:19 -07:00 committed by GitHub
parent f7d11ddecc
commit bd166030e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 470 additions and 329 deletions

View File

@ -11,10 +11,11 @@ import com.android.build.gradle.tasks.PackageAndroidArtifact
import com.android.build.gradle.tasks.ProcessAndroidResources
import com.android.builder.model.BuildType
import com.flutter.gradle.BaseApplicationNameHandler
import com.flutter.gradle.BaseFlutterTask
import com.flutter.gradle.Deeplink
import com.flutter.gradle.DependencyVersionChecker
import com.flutter.gradle.FlutterExtension
import com.flutter.gradle.FlutterPluginConstants
import com.flutter.gradle.FlutterTask
import com.flutter.gradle.FlutterPluginUtils
import com.flutter.gradle.IntentFilterCheck
import com.flutter.gradle.VersionUtils
@ -29,14 +30,7 @@ import org.gradle.api.Project
import org.gradle.api.Plugin
import org.gradle.api.Task
import org.gradle.api.UnknownTaskException
import org.gradle.api.file.CopySpec
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFiles
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Jar
import org.gradle.internal.os.OperatingSystem
@ -44,49 +38,6 @@ import org.gradle.internal.os.OperatingSystem
class FlutterPlugin implements Plugin<Project> {
private static final String DEFAULT_MAVEN_HOST = "https://storage.googleapis.com"
/** The platforms that can be passed to the `--Ptarget-platform` flag. */
private static final String PLATFORM_ARM32 = "android-arm"
private static final String PLATFORM_ARM64 = "android-arm64"
private static final String PLATFORM_X86 = "android-x86"
private static final String PLATFORM_X86_64 = "android-x64"
/** The ABI architectures supported by Flutter. */
private static final String ARCH_ARM32 = "armeabi-v7a"
private static final String ARCH_ARM64 = "arm64-v8a"
private static final String ARCH_X86 = "x86"
private static final String ARCH_X86_64 = "x86_64"
private static final String INTERMEDIATES_DIR = "intermediates"
/** Maps platforms to ABI architectures. */
private static final Map PLATFORM_ARCH_MAP = [
(PLATFORM_ARM32) : ARCH_ARM32,
(PLATFORM_ARM64) : ARCH_ARM64,
(PLATFORM_X86) : ARCH_X86,
(PLATFORM_X86_64) : ARCH_X86_64,
]
/**
* The version code that gives each ABI a value.
* For each APK variant, use the following versions to override the version of the Universal APK.
* Otherwise, the Play Store will complain that the APK variants have the same version.
*/
private static final Map<String, Integer> ABI_VERSION = [
(ARCH_ARM32) : 1,
(ARCH_ARM64) : 2,
(ARCH_X86) : 3,
(ARCH_X86_64) : 4,
]
/** When split is enabled, multiple APKs are generated per each ABI. */
private static final List DEFAULT_PLATFORMS = [
PLATFORM_ARM32,
PLATFORM_ARM64,
PLATFORM_X86_64,
]
private final static String propLocalEngineRepo = "local-engine-repo"
private final static String propProcessResourcesProvider = "processResourcesProvider"
@ -156,7 +107,7 @@ class FlutterPlugin implements Plugin<Project> {
}
// Configure the Maven repository.
String hostedRepository = System.getenv("FLUTTER_STORAGE_BASE_URL") ?: DEFAULT_MAVEN_HOST
String hostedRepository = System.getenv(FlutterPluginConstants.FLUTTER_STORAGE_BASE_URL) ?: FlutterPluginConstants.DEFAULT_MAVEN_HOST
String repository = FlutterPluginUtils.shouldProjectUseLocalEngine(project)
? project.property(propLocalEngineRepo)
: "$hostedRepository/${engineRealm}download.flutter.io"
@ -212,7 +163,7 @@ class FlutterPlugin implements Plugin<Project> {
}
getTargetPlatforms().each { targetArch ->
String abiValue = PLATFORM_ARCH_MAP[targetArch]
String abiValue = FlutterPluginConstants.PLATFORM_ARCH_MAP[targetArch]
project.android {
if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
splits {
@ -495,7 +446,7 @@ class FlutterPlugin implements Plugin<Project> {
}
List<String> platforms = getTargetPlatforms().collect()
platforms.each { platform ->
String arch = PLATFORM_ARCH_MAP[platform].replace("-", "_")
String arch = FlutterPluginUtils.formatPlatformString(platform)
// Add the `libflutter.so` dependency.
FlutterPluginUtils.addApiDependencies(project, buildType.name,
"io.flutter:${arch}_$flutterBuildMode:$engineVersion")
@ -851,10 +802,10 @@ class FlutterPlugin implements Plugin<Project> {
private List<String> getTargetPlatforms() {
final String propTargetPlatform = "target-platform"
if (!project.hasProperty(propTargetPlatform)) {
return DEFAULT_PLATFORMS
return FlutterPluginConstants.DEFAULT_PLATFORMS
}
return project.property(propTargetPlatform).split(",").collect {
if (!PLATFORM_ARCH_MAP[it]) {
if (!FlutterPluginConstants.PLATFORM_ARCH_MAP[it]) {
throw new GradleException("Invalid platform: $it.")
}
return it
@ -952,7 +903,7 @@ class FlutterPlugin implements Plugin<Project> {
// for only the output APK, not for the variant itself. Skipping this step simply
// causes Gradle to use the value of variant.versionCode for the APK.
// For more, see https://developer.android.com/studio/build/configure-apk-splits
Integer abiVersionCode = ABI_VERSION[output.getFilter(OutputFile.ABI)]
Integer abiVersionCode = FlutterPluginConstants.ABI_VERSION[output.getFilter(OutputFile.ABI)]
if (abiVersionCode != null) {
output.versionCodeOverride =
abiVersionCode * 1000 + variant.versionCode
@ -1002,7 +953,7 @@ class FlutterPlugin implements Plugin<Project> {
trackWidgetCreation(trackWidgetCreationValue)
targetPlatformValues = targetPlatforms
sourceDir(FlutterPluginUtils.getFlutterSourceDirectory(project))
intermediateDir(project.file(project.layout.buildDirectory.dir("$INTERMEDIATES_DIR/flutter/${variant.name}/")))
intermediateDir(project.file(project.layout.buildDirectory.dir("${FlutterPluginConstants.INTERMEDIATES_DIR}/flutter/${variant.name}/")))
frontendServerStarterPath(frontendServerStarterPathValue)
extraFrontEndOptions(extraFrontEndOptionsValue)
extraGenSnapshotOptions(extraGenSnapshotOptionsValue)
@ -1017,13 +968,13 @@ class FlutterPlugin implements Plugin<Project> {
flavor(flavorValue)
}
Task compileTask = compileTaskProvider.get()
File libJar = project.file(project.layout.buildDirectory.dir("$INTERMEDIATES_DIR/flutter/${variant.name}/libs.jar"))
File libJar = project.file(project.layout.buildDirectory.dir("${FlutterPluginConstants.INTERMEDIATES_DIR}/flutter/${variant.name}/libs.jar"))
TaskProvider<Jar> packJniLibsTaskProvider = project.tasks.register("packJniLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", Jar) {
destinationDirectory = libJar.parentFile
archiveFileName = libJar.name
dependsOn(compileTask)
targetPlatforms.each { targetPlatform ->
String abi = PLATFORM_ARCH_MAP[targetPlatform]
String abi = FlutterPluginConstants.PLATFORM_ARCH_MAP[targetPlatform]
from("${compileTask.intermediateDir}/${abi}") {
include("*.so")
// Move `app.so` to `lib/<abi>/libapp.so`
@ -1212,74 +1163,3 @@ class FlutterPlugin implements Plugin<Project> {
detectLowCompileSdkVersionOrNdkVersion()
}
}
class FlutterTask extends BaseFlutterTask {
@OutputDirectory
File getOutputDirectory() {
return intermediateDir
}
@Internal
String getAssetsDirectory() {
return "${outputDirectory}/flutter_assets"
}
@Internal
CopySpec getAssets() {
return project.copySpec {
from("${intermediateDir}")
include("flutter_assets/**") // the working dir and its files
}
}
@Internal
CopySpec getSnapshots() {
return project.copySpec {
from("${intermediateDir}")
if (buildMode == "release" || buildMode == "profile") {
targetPlatformValues.each {
include("${PLATFORM_ARCH_MAP[targetArch]}/app.so")
}
}
}
}
FileCollection readDependencies(File dependenciesFile, Boolean inputs) {
if (dependenciesFile.exists()) {
// Dependencies file has Makefile syntax:
// <target> <files>: <source> <files> <separated> <by> <non-escaped space>
String depText = dependenciesFile.text
// So we split list of files by non-escaped(by backslash) space,
def matcher = depText.split(": ")[inputs ? 1 : 0] =~ /(\\ |\S)+/
// then we replace all escaped spaces with regular spaces
def depList = matcher.collect{ it[0].replaceAll("\\\\ ", " ") }
return project.files(depList)
}
return project.files()
}
@InputFiles
FileCollection getSourceFiles() {
FileCollection sources = project.files()
for (File depfile in getDependenciesFiles()) {
sources += readDependencies(depfile, true)
}
return sources + project.files("pubspec.yaml")
}
@OutputFiles
FileCollection getOutputFiles() {
FileCollection sources = project.files()
for (File depfile in getDependenciesFiles()) {
sources += readDependencies(depfile, false)
}
return sources
}
@TaskAction
void build() {
buildBundle()
}
}

View File

@ -5,15 +5,20 @@
package com.flutter.gradle
import org.gradle.api.DefaultTask
import org.gradle.api.file.FileCollection
import org.gradle.api.logging.LogLevel
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFiles
import java.io.File
abstract class BaseFlutterTask : DefaultTask() {
// IMPORTANT: Do not add logic to the methods in this class directly,
// instead add logic to [BaseFlutterTaskHelper].
/**
* Base implementation of a Gradle task. Gradle tasks can not be instantiated for testing,
* so this class delegates all logic to [BaseFlutterTaskHelper].
*/
open class BaseFlutterTask : DefaultTask() {
@Internal
var flutterRoot: File? = null
@ -132,21 +137,12 @@ abstract class BaseFlutterTask : DefaultTask() {
* @return the dependency file(s) based on the current intermediate directory path.
*/
@OutputFiles
fun getDependenciesFiles(): FileCollection {
val helper = BaseFlutterTaskHelper(baseFlutterTask = this)
val depFiles = helper.getDependenciesFiles()
return depFiles
}
fun getDependenciesFiles() = BaseFlutterTaskHelper.getDependenciesFiles(baseFlutterTask = this)
/**
* Builds a Flutter Android application bundle by verifying the Flutter source directory,
* creating an intermediate build directory if necessary, and running flutter assemble by
* configuring and executing with a set of build configurations.
*/
fun buildBundle() {
val helper = BaseFlutterTaskHelper(baseFlutterTask = this)
helper.checkPreConditions()
logging.captureStandardError(LogLevel.ERROR)
project.exec(helper.createExecSpecActionFromTask())
}
fun buildBundle() = BaseFlutterTaskHelper.buildBundle(baseFlutterTask = this)
}

View File

@ -4,15 +4,19 @@ import androidx.annotation.VisibleForTesting
import org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.file.FileCollection
import org.gradle.api.logging.LogLevel
import org.gradle.api.tasks.OutputFiles
import org.gradle.process.ExecSpec
import java.nio.file.Paths
class BaseFlutterTaskHelper(
private var baseFlutterTask: BaseFlutterTask
) {
/**
* Stateless object to contain the logic used in [BaseFlutterTask]. Any required state should be stored
* on [BaseFlutterTask] instead, while any logic needed by [BaseFlutterTask] should be added here.
*/
object BaseFlutterTaskHelper {
@VisibleForTesting
internal var gradleErrorMessage = "Invalid Flutter source directory: ${baseFlutterTask.sourceDir}"
internal fun getGradleErrorMessage(baseFlutterTask: BaseFlutterTask): String =
"Invalid Flutter source directory: ${baseFlutterTask.sourceDir}"
/**
* Gets the dependency file(s) that tracks the dependencies or input files used for a specific
@ -22,7 +26,7 @@ class BaseFlutterTaskHelper(
*/
@OutputFiles
@VisibleForTesting
internal fun getDependenciesFiles(): FileCollection {
internal fun getDependenciesFiles(baseFlutterTask: BaseFlutterTask): FileCollection {
var depfiles: FileCollection = baseFlutterTask.project.files()
// TODO(jesswon): During cleanup determine if .../flutter_build.d is ever a directory and refactor accordingly
@ -38,9 +42,9 @@ class BaseFlutterTaskHelper(
* @throws GradleException if sourceDir is null or is not a directory
*/
@VisibleForTesting
internal fun checkPreConditions() {
internal fun checkPreConditions(baseFlutterTask: BaseFlutterTask) {
if (baseFlutterTask.sourceDir == null || !baseFlutterTask.sourceDir!!.isDirectory) {
throw GradleException(gradleErrorMessage)
throw GradleException(getGradleErrorMessage(baseFlutterTask))
}
baseFlutterTask.intermediateDir!!.mkdirs()
}
@ -77,8 +81,7 @@ class BaseFlutterTaskHelper(
*
* @return an Action<ExecSpec> of build processes and options to be executed.
*/
@VisibleForTesting
internal fun createExecSpecActionFromTask(): Action<ExecSpec> =
internal fun createExecSpecActionFromTask(baseFlutterTask: BaseFlutterTask): Action<ExecSpec> =
Action<ExecSpec> {
executable(baseFlutterTask.flutterExecutable!!.absolutePath)
workingDir(baseFlutterTask.sourceDir)
@ -146,4 +149,10 @@ class BaseFlutterTaskHelper(
args("-dMinSdkVersion=${baseFlutterTask.minSdkVersion}")
args(generateRuleNames(baseFlutterTask))
}
fun buildBundle(baseFlutterTask: BaseFlutterTask) {
checkPreConditions(baseFlutterTask)
baseFlutterTask.logging.captureStandardError(LogLevel.ERROR)
baseFlutterTask.project.exec(createExecSpecActionFromTask(baseFlutterTask = baseFlutterTask))
}
}

View File

@ -0,0 +1,51 @@
package com.flutter.gradle
// TODO(gmackall): this should be collapsed back into the core FlutterPlugin once the Groovy to
// kotlin conversion is complete.
object FlutterPluginConstants {
/** The platforms that can be passed to the `--Ptarget-platform` flag. */
private const val PLATFORM_ARM32 = "android-arm"
private const val PLATFORM_ARM64 = "android-arm64"
private const val PLATFORM_X86 = "android-x86"
private const val PLATFORM_X86_64 = "android-x64"
/** The ABI architectures supported by Flutter. */
private const val ARCH_ARM32 = "armeabi-v7a"
private const val ARCH_ARM64 = "arm64-v8a"
private const val ARCH_X86 = "x86"
private const val ARCH_X86_64 = "x86_64"
const val INTERMEDIATES_DIR = "intermediates"
const val FLUTTER_STORAGE_BASE_URL = "FLUTTER_STORAGE_BASE_URL"
const val DEFAULT_MAVEN_HOST = "https://storage.googleapis.com"
/** Maps platforms to ABI architectures. */
@JvmStatic val PLATFORM_ARCH_MAP =
mapOf(
PLATFORM_ARM32 to ARCH_ARM32,
PLATFORM_ARM64 to ARCH_ARM64,
PLATFORM_X86 to ARCH_X86,
PLATFORM_X86_64 to ARCH_X86_64
)
/**
* The version code that gives each ABI a value.
* For each APK variant, use the following versions to override the version of the Universal APK.
* Otherwise, the Play Store will complain that the APK variants have the same version.
*/
@JvmStatic val ABI_VERSION =
mapOf<String, Int>( // Explicit type for clarity, though inferred
ARCH_ARM32 to 1,
ARCH_ARM64 to 2,
ARCH_X86 to 3,
ARCH_X86_64 to 4
)
/** When split is enabled, multiple APKs are generated per each ABI. */
@JvmStatic val DEFAULT_PLATFORMS =
listOf(
PLATFORM_ARM32,
PLATFORM_ARM64,
PLATFORM_X86_64
)
}

View File

@ -85,6 +85,10 @@ object FlutterPluginUtils {
return firstVersion.size.compareTo(secondVersion.size)
}
@JvmStatic
@JvmName("formatPlatformString")
fun formatPlatformString(platform: String): String = FlutterPluginConstants.PLATFORM_ARCH_MAP[platform]!!.replace("-", "_")
// ----------------- Methods that interact primarily with the Gradle project. -----------------
@JvmStatic

View File

@ -0,0 +1,46 @@
package com.flutter.gradle
import org.gradle.api.file.CopySpec
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFiles
import org.gradle.api.tasks.TaskAction
import java.io.File
// IMPORTANT: Do not add logic to the methods in this class directly,
// instead add logic to [FlutterTaskHelper].
/**
* Flutter's implementation of a Gradle task. Gradle tasks can not be instantiated for testing,
* so this class delegates all logic to [FlutterTaskHelper].
*/
abstract class FlutterTask : BaseFlutterTask() {
@get:OutputDirectory
val outputDirectory: File?
get() = FlutterTaskHelper.getOutputDirectory(flutterTask = this)
@get:Internal
val assetsDirectory: String
get() = FlutterTaskHelper.getAssetsDirectory(flutterTask = this)
@get:Internal
val assets: CopySpec
get() = FlutterTaskHelper.getAssets(project, flutterTask = this)
@get:Internal
val snapshots: CopySpec
get() = FlutterTaskHelper.getSnapshots(project, flutterTask = this)
@get:InputFiles
val sourceFiles: FileCollection
get() = FlutterTaskHelper.getSourceFiles(project, flutterTask = this)
@get:OutputFiles
val outputFiles: FileCollection
get() = FlutterTaskHelper.getOutputFiles(project, flutterTask = this)
@TaskAction
fun build() = FlutterTaskHelper.build(flutterTask = this)
}

View File

@ -0,0 +1,87 @@
package com.flutter.gradle
import org.gradle.api.Project
import org.gradle.api.file.CopySpec
import org.gradle.api.file.FileCollection
import java.io.File
/**
* Stateless object to contain the logic used in [FlutterTask]. Any required state should be stored
* on [FlutterTask] instead, while any logic needed by [FlutterTask] should be added here.
*/
object FlutterTaskHelper {
const val FLUTTER_ASSETS_INCLUDE_DIRECTORY = "flutter_assets/**"
internal fun getOutputDirectory(flutterTask: FlutterTask): File? = flutterTask.intermediateDir
internal fun getAssetsDirectory(flutterTask: FlutterTask): String = "${flutterTask.outputDirectory}/flutter_assets"
internal fun getAssets(
project: Project,
flutterTask: FlutterTask
): CopySpec =
project.copySpec {
from("${flutterTask.intermediateDir}")
include(FLUTTER_ASSETS_INCLUDE_DIRECTORY) // the working dir and its files
}
internal fun getSnapshots(
project: Project,
flutterTask: FlutterTask
): CopySpec =
project.copySpec {
from("${flutterTask.intermediateDir}")
if (flutterTask.buildMode == "release" || flutterTask.buildMode == "profile") {
flutterTask.targetPlatformValues!!.forEach { targetArch ->
include("${FlutterPluginConstants.PLATFORM_ARCH_MAP[targetArch]}/app.so")
}
}
}
private fun readDependencies(
project: Project,
dependenciesFile: File,
inputs: Boolean
): FileCollection {
if (dependenciesFile.exists()) {
// Dependencies file has Makefile syntax:
// <target> <files>: <source> <files> <separated> <by> <non-escaped space>
val depText = dependenciesFile.readText()
// So we split list of files by non-escaped(by backslash) space,
val parts = depText.split(": ")
val fileString = parts[if (inputs) 1 else 0]
val matcher = Regex("""(\\ |\S)+""").findAll(fileString)
// then we replace all escaped spaces with regular spaces
val depList =
matcher.map { it.value.replace("\\\\ ", " ") }.toList()
return project.files(depList)
}
return project.files()
}
internal fun getSourceFiles(
project: Project,
flutterTask: FlutterTask
): FileCollection {
var sources: FileCollection = project.files()
flutterTask.getDependenciesFiles().forEach { dependenciesFile ->
sources += readDependencies(project, dependenciesFile, true)
}
return sources + project.files("pubspec.yaml")
}
internal fun getOutputFiles(
project: Project,
flutterTask: FlutterTask
): FileCollection {
var outputs: FileCollection = project.files()
flutterTask.getDependenciesFiles().forEach { dependenciesFile ->
outputs += readDependencies(project, dependenciesFile, false)
}
return outputs
}
internal fun build(flutterTask: FlutterTask) {
flutterTask.buildBundle()
}
}

View File

@ -3,7 +3,11 @@ package com.flutter.gradle
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.logging.LoggingManager
import org.gradle.process.ExecSpec
import org.gradle.process.ProcessForkOptions
import org.junit.jupiter.api.assertDoesNotThrow
@ -48,13 +52,11 @@ class BaseFlutterTaskHelperTest {
val baseFlutterTask = mockk<BaseFlutterTask>()
every { baseFlutterTask.sourceDir } returns null
val helper = BaseFlutterTaskHelper(baseFlutterTask)
val gradleException =
assertFailsWith<GradleException> { helper.checkPreConditions() }
assertFailsWith<GradleException> { BaseFlutterTaskHelper.checkPreConditions(baseFlutterTask) }
assert(
gradleException.message ==
helper.gradleErrorMessage
BaseFlutterTaskHelper.getGradleErrorMessage(baseFlutterTask)
)
}
@ -64,13 +66,11 @@ class BaseFlutterTaskHelperTest {
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
every { baseFlutterTask.sourceDir!!.isDirectory } returns false
val helper = BaseFlutterTaskHelper(baseFlutterTask)
val gradleException =
assertFailsWith<GradleException> { helper.checkPreConditions() }
assertFailsWith<GradleException> { BaseFlutterTaskHelper.checkPreConditions(baseFlutterTask) }
assert(
gradleException.message ==
helper.gradleErrorMessage
BaseFlutterTaskHelper.getGradleErrorMessage(baseFlutterTask)
)
}
@ -86,8 +86,7 @@ class BaseFlutterTaskHelperTest {
// There is already an intermediate directory, so there is no need to create it.
every { baseFlutterTask.intermediateDir!!.mkdirs() } returns false
val helper = BaseFlutterTaskHelper(baseFlutterTask)
assertDoesNotThrow { helper.checkPreConditions() }
assertDoesNotThrow { BaseFlutterTaskHelper.checkPreConditions(baseFlutterTask) }
}
@Test
@ -100,8 +99,7 @@ class BaseFlutterTaskHelperTest {
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
every { baseFlutterTask.buildMode } returns buildModeString
val helper = BaseFlutterTaskHelper(baseFlutterTask)
val ruleNamesList = helper.generateRuleNames(baseFlutterTask)
val ruleNamesList = BaseFlutterTaskHelper.generateRuleNames(baseFlutterTask)
assertEquals(ruleNamesList, listOf("debug_android_application"))
}
@ -118,8 +116,7 @@ class BaseFlutterTaskHelperTest {
every { baseFlutterTask.deferredComponents } returns true
every { baseFlutterTask.targetPlatformValues } returns BaseFlutterTaskPropertiesTest.targetPlatformValuesList
val helper = BaseFlutterTaskHelper(baseFlutterTask)
val ruleNamesList = helper.generateRuleNames(baseFlutterTask)
val ruleNamesList = BaseFlutterTaskHelper.generateRuleNames(baseFlutterTask)
assertEquals(
ruleNamesList,
@ -142,8 +139,7 @@ class BaseFlutterTaskHelperTest {
every { baseFlutterTask.deferredComponents } returns false
every { baseFlutterTask.targetPlatformValues } returns BaseFlutterTaskPropertiesTest.targetPlatformValuesList
val helper = BaseFlutterTaskHelper(baseFlutterTask)
val ruleNamesList = helper.generateRuleNames(baseFlutterTask)
val ruleNamesList = BaseFlutterTaskHelper.generateRuleNames(baseFlutterTask)
assertEquals(
ruleNamesList,
@ -166,8 +162,7 @@ class BaseFlutterTaskHelperTest {
// When baseFlutterTask.sourceDir is null, an exception is thrown. We mock its return value
// before creating a BaseFlutterTaskHelper object.
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
val helper = BaseFlutterTaskHelper(baseFlutterTask)
val execSpecActionFromTask = helper.createExecSpecActionFromTask()
val execSpecActionFromTask = BaseFlutterTaskHelper.createExecSpecActionFromTask(baseFlutterTask)
// Mock return values of properties.
every { baseFlutterTask.flutterExecutable } returns BaseFlutterTaskPropertiesTest.flutterExecutableTest
@ -216,7 +211,7 @@ class BaseFlutterTaskHelperTest {
every { mockExecSpec.args(any<List<String>>()) } returns mockExecSpec
// Generate rule names for verification and can only be generated after buildMode is mocked.
val ruleNamesList: List<String> = helper.generateRuleNames(baseFlutterTask)
val ruleNamesList: List<String> = BaseFlutterTaskHelper.generateRuleNames(baseFlutterTask)
// The exec function will be deprecated in gradle 8.11 and will be removed in gradle 9.0
// https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.kotlin.dsl/-kotlin-script/exec.html?query=abstract%20fun%20exec(configuration:%20Action%3CExecSpec%3E):%20ExecResult
@ -267,8 +262,7 @@ class BaseFlutterTaskHelperTest {
// When baseFlutterTask.sourceDir is null, an exception is thrown. We mock its return value
// before creating a BaseFlutterTaskHelper object.
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
val helper = BaseFlutterTaskHelper(baseFlutterTask)
val execSpecActionFromTask = helper.createExecSpecActionFromTask()
val execSpecActionFromTask = BaseFlutterTaskHelper.createExecSpecActionFromTask(baseFlutterTask)
// Mock return values of properties.
every { baseFlutterTask.flutterExecutable } returns BaseFlutterTaskPropertiesTest.flutterExecutableTest
@ -317,7 +311,7 @@ class BaseFlutterTaskHelperTest {
every { mockExecSpec.args(any<List<String>>()) } returns mockExecSpec
// Generate rule names for verification and can only be generated after buildMode is mocked.
val ruleNamesList: List<String> = helper.generateRuleNames(baseFlutterTask)
val ruleNamesList: List<String> = BaseFlutterTaskHelper.generateRuleNames(baseFlutterTask)
// The exec function will be deprecated in gradle 8.11 and will be removed in gradle 9.0
// https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.kotlin.dsl/-kotlin-script/exec.html?query=abstract%20fun%20exec(configuration:%20Action%3CExecSpec%3E):%20ExecResult
@ -353,8 +347,7 @@ class BaseFlutterTaskHelperTest {
// When baseFlutterTask.sourceDir is null, an exception is thrown. We mock its return value
// before creating a BaseFlutterTaskHelper object.
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
val helper = BaseFlutterTaskHelper(baseFlutterTask)
val execSpecActionFromTask = helper.createExecSpecActionFromTask()
val execSpecActionFromTask = BaseFlutterTaskHelper.createExecSpecActionFromTask(baseFlutterTask)
// Mock return values of properties.
every { baseFlutterTask.flutterExecutable } returns BaseFlutterTaskPropertiesTest.flutterExecutableTest
@ -402,7 +395,7 @@ class BaseFlutterTaskHelperTest {
every { mockExecSpec.args(any<List<String>>()) } returns mockExecSpec
// Generate rule names for verification and can only be generated after buildMode is mocked.
val ruleNamesList: List<String> = helper.generateRuleNames(baseFlutterTask)
val ruleNamesList: List<String> = BaseFlutterTaskHelper.generateRuleNames(baseFlutterTask)
// The exec function will be deprecated in gradle 8.11 and will be removed in gradle 9.0
// https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.kotlin.dsl/-kotlin-script/exec.html?query=abstract%20fun%20exec(configuration:%20Action%3CExecSpec%3E):%20ExecResult
@ -453,8 +446,7 @@ class BaseFlutterTaskHelperTest {
// When baseFlutterTask.sourceDir is null, an exception is thrown. We mock its return value
// before creating a BaseFlutterTaskHelper object.
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
val helper = BaseFlutterTaskHelper(baseFlutterTask)
val execSpecActionFromTask = helper.createExecSpecActionFromTask()
val execSpecActionFromTask = BaseFlutterTaskHelper.createExecSpecActionFromTask(baseFlutterTask)
// Mock return values of properties.
every { baseFlutterTask.flutterExecutable } returns BaseFlutterTaskPropertiesTest.flutterExecutableTest
@ -503,7 +495,7 @@ class BaseFlutterTaskHelperTest {
every { mockExecSpec.args(any<List<String>>()) } returns mockExecSpec
// Generate rule names for verification and can only be generated after buildMode is mocked.
val ruleNamesList: List<String> = helper.generateRuleNames(baseFlutterTask)
val ruleNamesList: List<String> = BaseFlutterTaskHelper.generateRuleNames(baseFlutterTask)
// The exec function will be deprecated in gradle 8.11 and will be removed in gradle 9.0
// https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.kotlin.dsl/-kotlin-script/exec.html?query=abstract%20fun%20exec(configuration:%20Action%3CExecSpec%3E):%20ExecResult
@ -541,4 +533,46 @@ class BaseFlutterTaskHelperTest {
verify { mockExecSpec.args("-dMinSdkVersion=${BaseFlutterTaskPropertiesTest.MIN_SDK_VERSION_TEST}") }
verify { mockExecSpec.args(ruleNamesList) }
}
@Test
fun `buildBundle calls the correct methods`() {
val baseFlutterTask = mockk<BaseFlutterTask>()
val mockLoggingManager = mockk<LoggingManager>()
val mockFile = mockk<File>()
val mockProject = mockk<Project>()
// When baseFlutterTask.sourceDir is null, an exception is thrown. We mock its return value
// before creating a BaseFlutterTaskHelper object.
every { baseFlutterTask.sourceDir } returns mockFile
every { mockFile.isDirectory } returns true
every { baseFlutterTask.intermediateDir } returns BaseFlutterTaskPropertiesTest.intermediateDirFileTest
every { baseFlutterTask.logging } returns mockLoggingManager
every { mockLoggingManager.captureStandardError(any()) } returns mockLoggingManager
every { baseFlutterTask.project } returns mockProject
every { mockProject.exec(any<Action<ExecSpec>>()) } returns mockk()
BaseFlutterTaskHelper.buildBundle(baseFlutterTask)
}
@Test
fun `getDependencyFiles returns a FileCollection of dependency file(s)`() {
val baseFlutterTask = mockk<BaseFlutterTask>()
val project = mockk<Project>()
val configFileCollection = mockk<ConfigurableFileCollection>()
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
every { baseFlutterTask.project } returns project
every { baseFlutterTask.intermediateDir } returns BaseFlutterTaskPropertiesTest.intermediateDirFileTest
val projectIntermediary = baseFlutterTask.project
val interDirFile = baseFlutterTask.intermediateDir
every { projectIntermediary.files() } returns configFileCollection
every { projectIntermediary.files("$interDirFile/flutter_build.d") } returns configFileCollection
every { configFileCollection.plus(configFileCollection) } returns configFileCollection
BaseFlutterTaskHelper.getDependenciesFiles(baseFlutterTask)
verify { projectIntermediary.files() }
verify { projectIntermediary.files("${BaseFlutterTaskPropertiesTest.intermediateDirFileTest}/flutter_build.d") }
}
}

View File

@ -1,147 +0,0 @@
package com.flutter.gradle
import com.flutter.gradle.BaseFlutterTaskHelperTest.BaseFlutterTaskPropertiesTest
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.process.ExecSpec
import org.gradle.process.ProcessForkOptions
import org.junit.jupiter.api.assertDoesNotThrow
import kotlin.test.Test
class BaseFlutterTaskTest {
@Test
fun `getDependencyFiles returns a FileCollection of dependency file(s)`() {
val baseFlutterTask = mockk<BaseFlutterTask>()
val project = mockk<Project>()
val configFileCollection = mockk<ConfigurableFileCollection>()
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
val helper = BaseFlutterTaskHelper(baseFlutterTask)
every { baseFlutterTask.project } returns project
every { baseFlutterTask.intermediateDir } returns BaseFlutterTaskPropertiesTest.intermediateDirFileTest
val projectIntermediary = baseFlutterTask.project
val interDirFile = baseFlutterTask.intermediateDir
every { projectIntermediary.files() } returns configFileCollection
every { projectIntermediary.files("$interDirFile/flutter_build.d") } returns configFileCollection
every { configFileCollection.plus(configFileCollection) } returns configFileCollection
helper.getDependenciesFiles()
verify { projectIntermediary.files() }
verify { projectIntermediary.files("${BaseFlutterTaskPropertiesTest.intermediateDirFileTest}/flutter_build.d") }
}
@Test
fun `buildBundle builds a Flutter application bundle for Android`() {
val buildModeString = "debug"
// Create necessary mocks.
val baseFlutterTask = mockk<BaseFlutterTask>()
val mockExecSpec = mockk<ExecSpec>()
val mockProcessForkOptions = mockk<ProcessForkOptions>()
// Check preconditions
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
every { baseFlutterTask.sourceDir!!.isDirectory } returns true
every { baseFlutterTask.intermediateDir } returns BaseFlutterTaskPropertiesTest.intermediateDirFileTest
every { baseFlutterTask.intermediateDir!!.mkdirs() } returns false
val helper = BaseFlutterTaskHelper(baseFlutterTask)
assertDoesNotThrow { helper.checkPreConditions() }
// Create action to be executed.
val execSpecActionFromTask = helper.createExecSpecActionFromTask()
// Mock return values of properties.
every { baseFlutterTask.flutterExecutable } returns BaseFlutterTaskPropertiesTest.flutterExecutableTest
every {
baseFlutterTask.flutterExecutable!!.absolutePath
} returns BaseFlutterTaskPropertiesTest.FLUTTER_EXECUTABLE_ABSOLUTE_PATH_TEST
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
every { baseFlutterTask.localEngine } returns BaseFlutterTaskPropertiesTest.LOCAL_ENGINE_TEST
every { baseFlutterTask.localEngineSrcPath } returns BaseFlutterTaskPropertiesTest.LOCAL_ENGINE_SRC_PATH_TEST
every { baseFlutterTask.localEngineHost } returns BaseFlutterTaskPropertiesTest.LOCAL_ENGINE_HOST_TEST
every { baseFlutterTask.verbose } returns true
every { baseFlutterTask.intermediateDir } returns BaseFlutterTaskPropertiesTest.intermediateDirFileTest
every { baseFlutterTask.performanceMeasurementFile } returns BaseFlutterTaskPropertiesTest.PERFORMANCE_MEASUREMENT_FILE_TEST
every { baseFlutterTask.fastStart } returns true
every { baseFlutterTask.buildMode } returns buildModeString
every { baseFlutterTask.flutterRoot } returns BaseFlutterTaskPropertiesTest.flutterRootTest
every { baseFlutterTask.flutterRoot!!.absolutePath } returns BaseFlutterTaskPropertiesTest.FLUTTER_ROOT_ABSOLUTE_PATH_TEST
every { baseFlutterTask.trackWidgetCreation } returns true
every { baseFlutterTask.splitDebugInfo } returns BaseFlutterTaskPropertiesTest.SPLIT_DEBUG_INFO_TEST
every { baseFlutterTask.treeShakeIcons } returns true
every { baseFlutterTask.dartObfuscation } returns true
every { baseFlutterTask.dartDefines } returns BaseFlutterTaskPropertiesTest.DART_DEFINES_TEST
every { baseFlutterTask.bundleSkSLPath } returns BaseFlutterTaskPropertiesTest.BUNDLE_SK_SL_PATH_TEST
every { baseFlutterTask.codeSizeDirectory } returns BaseFlutterTaskPropertiesTest.CODE_SIZE_DIRECTORY_TEST
every { baseFlutterTask.flavor } returns BaseFlutterTaskPropertiesTest.FLAVOR_TEST
every { baseFlutterTask.extraGenSnapshotOptions } returns BaseFlutterTaskPropertiesTest.EXTRA_GEN_SNAPSHOT_OPTIONS_TEST
every { baseFlutterTask.frontendServerStarterPath } returns BaseFlutterTaskPropertiesTest.FRONTEND_SERVER_STARTER_PATH_TEST
every { baseFlutterTask.extraFrontEndOptions } returns BaseFlutterTaskPropertiesTest.EXTRA_FRONTEND_OPTIONS_TEST
every { baseFlutterTask.targetPlatformValues } returns BaseFlutterTaskPropertiesTest.targetPlatformValuesList
every { baseFlutterTask.minSdkVersion } returns BaseFlutterTaskPropertiesTest.MIN_SDK_VERSION_TEST
// Mock the method calls. We collapse all the args mock calls into four calls.
every { mockExecSpec.executable(any<String>()) } returns mockExecSpec
every { mockExecSpec.workingDir(any()) } returns mockProcessForkOptions
every { mockExecSpec.args(any<String>(), any()) } returns mockExecSpec
every { mockExecSpec.args(any<String>(), any()) } returns mockExecSpec
every { mockExecSpec.args(any<String>()) } returns mockExecSpec
every { mockExecSpec.args(any<List<String>>()) } returns mockExecSpec
// Generate rule names for verification and can only be generated after buildMode is mocked.
val ruleNamesList: List<String> = helper.generateRuleNames(baseFlutterTask)
// The exec function will be deprecated in gradle 8.11 and will be removed in gradle 9.0
// https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.kotlin.dsl/-kotlin-script/exec.html?query=abstract%20fun%20exec(configuration:%20Action%3CExecSpec%3E):%20ExecResult
// The actions are executed.
execSpecActionFromTask.execute(mockExecSpec)
// After execution, we verify the functions are actually being
// called with the expected argument passed in.
verify { mockExecSpec.executable(BaseFlutterTaskPropertiesTest.FLUTTER_EXECUTABLE_ABSOLUTE_PATH_TEST) }
verify { mockExecSpec.workingDir(BaseFlutterTaskPropertiesTest.sourceDirTest) }
verify { mockExecSpec.args("--local-engine", BaseFlutterTaskPropertiesTest.LOCAL_ENGINE_TEST) }
verify { mockExecSpec.args("--local-engine-src-path", BaseFlutterTaskPropertiesTest.LOCAL_ENGINE_SRC_PATH_TEST) }
verify { mockExecSpec.args("--local-engine-host", BaseFlutterTaskPropertiesTest.LOCAL_ENGINE_HOST_TEST) }
verify { mockExecSpec.args("--verbose") }
verify { mockExecSpec.args("assemble") }
verify { mockExecSpec.args("--no-version-check") }
verify { mockExecSpec.args("--depfile", "${BaseFlutterTaskPropertiesTest.intermediateDirFileTest}/flutter_build.d") }
verify { mockExecSpec.args("--output", "${BaseFlutterTaskPropertiesTest.intermediateDirFileTest}") }
verify { mockExecSpec.args("--performance-measurement-file=${BaseFlutterTaskPropertiesTest.PERFORMANCE_MEASUREMENT_FILE_TEST}") }
verify { mockExecSpec.args("-dTargetFile=${BaseFlutterTaskPropertiesTest.FLUTTER_TARGET_FILE_PATH}") }
verify { mockExecSpec.args("-dTargetPlatform=android") }
verify { mockExecSpec.args("-dBuildMode=$buildModeString") }
verify { mockExecSpec.args("-dTrackWidgetCreation=${true}") }
verify { mockExecSpec.args("-dSplitDebugInfo=${BaseFlutterTaskPropertiesTest.SPLIT_DEBUG_INFO_TEST}") }
verify { mockExecSpec.args("-dTreeShakeIcons=true") }
verify { mockExecSpec.args("-dDartObfuscation=true") }
verify { mockExecSpec.args("--DartDefines=${BaseFlutterTaskPropertiesTest.DART_DEFINES_TEST}") }
verify { mockExecSpec.args("-dBundleSkSLPath=${BaseFlutterTaskPropertiesTest.BUNDLE_SK_SL_PATH_TEST}") }
verify { mockExecSpec.args("-dCodeSizeDirectory=${BaseFlutterTaskPropertiesTest.CODE_SIZE_DIRECTORY_TEST}") }
verify { mockExecSpec.args("-dFlavor=${BaseFlutterTaskPropertiesTest.FLAVOR_TEST}") }
verify { mockExecSpec.args("--ExtraGenSnapshotOptions=${BaseFlutterTaskPropertiesTest.EXTRA_GEN_SNAPSHOT_OPTIONS_TEST}") }
verify { mockExecSpec.args("-dFrontendServerStarterPath=${BaseFlutterTaskPropertiesTest.FRONTEND_SERVER_STARTER_PATH_TEST}") }
verify { mockExecSpec.args("--ExtraFrontEndOptions=${BaseFlutterTaskPropertiesTest.EXTRA_FRONTEND_OPTIONS_TEST}") }
verify { mockExecSpec.args("-dAndroidArchs=${BaseFlutterTaskPropertiesTest.TARGET_PLATFORM_VALUES_JOINED_LIST}") }
verify { mockExecSpec.args("-dMinSdkVersion=${BaseFlutterTaskPropertiesTest.MIN_SDK_VERSION_TEST}") }
verify { mockExecSpec.args(ruleNamesList) }
}
}

View File

@ -63,6 +63,13 @@ class FlutterPluginUtilsTest {
assertEquals(0, result)
}
// formatPlatformString
@Test
fun `formatPlatformString returns correct string`() {
val result = FlutterPluginUtils.formatPlatformString("android-arm64")
assertEquals("arm64_v8a", result)
}
// shouldShrinkResources
@Test
fun `shouldShrinkResources returns true by default`() {

View File

@ -0,0 +1,174 @@
package com.flutter.gradle
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.CopySpec
import org.gradle.api.file.FileCollection
import org.junit.jupiter.api.io.TempDir
import java.io.File
import java.nio.file.Path
import kotlin.test.Test
class FlutterTaskHelperTest {
@Test
fun `getAssetsDirectory returns correct path`() {
val flutterTask = mockk<FlutterTask>()
val mockFile = mockk<File>()
val flutterTaskOutputDirectory = "/path/to/assets"
val expectedPath = "$flutterTaskOutputDirectory/flutter_assets"
every { flutterTask.outputDirectory } returns mockFile
every { mockFile.toString() } returns flutterTaskOutputDirectory
val result = FlutterTaskHelper.getAssetsDirectory(flutterTask)
assert(result == expectedPath)
}
@Test
fun `getAssets returns correct CopySpec`() {
val project = mockk<Project>()
val flutterTask = mockk<FlutterTask>()
val mockFile = mockk<File>()
val mockCopySpec = mockk<CopySpec>()
val copySpecActionSlot = slot<Action<in CopySpec>>()
val fakeFromPath = "/path/to/intermediate"
every { flutterTask.intermediateDir } returns mockFile
every { mockFile.toString() } returns fakeFromPath
every { project.copySpec(capture(copySpecActionSlot)) } returns mockk()
FlutterTaskHelper.getAssets(project, flutterTask)
every { mockCopySpec.from(fakeFromPath) } returns mockCopySpec
every { mockCopySpec.include(FlutterTaskHelper.FLUTTER_ASSETS_INCLUDE_DIRECTORY) } returns mockCopySpec
copySpecActionSlot.captured.execute(mockCopySpec)
verify { mockCopySpec.from(fakeFromPath) }
verify { mockCopySpec.include(FlutterTaskHelper.FLUTTER_ASSETS_INCLUDE_DIRECTORY) }
}
@Test
fun `getSnapshots returns correct CopySpec for release build`() {
val project = mockk<Project>()
val flutterTask = mockk<FlutterTask>()
val mockCopySpec = mockk<CopySpec>()
val copySpecActionSlot = slot<Action<in CopySpec>>()
val fakeIntermediateDirectory = mockk<File>()
val fakeIntermediateDirectoryPath = "/path/to/intermediate"
every { flutterTask.intermediateDir } returns fakeIntermediateDirectory
every { flutterTask.buildMode } returns "release"
every { flutterTask.targetPlatformValues } returns listOf("arm64-v8a", "x64")
every { fakeIntermediateDirectory.toString() } returns fakeIntermediateDirectoryPath
every { project.copySpec(capture(copySpecActionSlot)) } returns mockk()
FlutterTaskHelper.getSnapshots(project, flutterTask)
every { mockCopySpec.from(fakeIntermediateDirectoryPath) } returns mockCopySpec
every { mockCopySpec.include(any<String>()) } returns mockCopySpec
copySpecActionSlot.captured.execute(mockCopySpec)
verify { mockCopySpec.from(fakeIntermediateDirectoryPath) }
verify { mockCopySpec.include("${FlutterPluginConstants.PLATFORM_ARCH_MAP["arm64-v8a"]}/app.so") }
verify { mockCopySpec.include("${FlutterPluginConstants.PLATFORM_ARCH_MAP["x64"]}/app.so") }
}
@Test
fun `getSnapshots returns correct CopySpec for debug build`() {
val project = mockk<Project>()
val flutterTask = mockk<FlutterTask>()
val mockCopySpec = mockk<CopySpec>()
val copySpecActionSlot = slot<Action<in CopySpec>>()
val fakeIntermediateDirectory = mockk<File>()
val fakeIntermediateDirectoryPath = "/path/to/intermediate"
every { flutterTask.intermediateDir } returns fakeIntermediateDirectory
every { flutterTask.buildMode } returns "debug"
every { flutterTask.targetPlatformValues } returns listOf("arm64-v8a", "x64")
every { fakeIntermediateDirectory.toString() } returns fakeIntermediateDirectoryPath
every { project.copySpec(capture(copySpecActionSlot)) } returns mockk()
FlutterTaskHelper.getSnapshots(project, flutterTask)
every { mockCopySpec.from(fakeIntermediateDirectoryPath) } returns mockCopySpec
every { mockCopySpec.include(any<String>()) } returns mockCopySpec
copySpecActionSlot.captured.execute(mockCopySpec)
verify { mockCopySpec.from(fakeIntermediateDirectoryPath) }
verify(exactly = 0) { mockCopySpec.include(any<String>()) }
}
@Test
fun `getSourceFiles returns files when dependenciesFile exists`(
@TempDir tempDir: Path
) {
val mockProjectFileCollection = mockk<ConfigurableFileCollection>(relaxed = true)
val mockDependenciesFileCollection = mockk<FileCollection>()
val project = mockk<Project>()
val mockFlutterTask = mockk<FlutterTask>()
every { project.files() } returns mockProjectFileCollection
every { project.files(any()) } returns mockProjectFileCollection
every { mockFlutterTask.intermediateDir } returns tempDir.toFile()
every { mockFlutterTask.getDependenciesFiles() } returns mockDependenciesFileCollection
val dependenciesFile =
tempDir
.resolve("${mockFlutterTask.intermediateDir}/flutter_build.d")
.toFile()
dependenciesFile.writeText(
" ${tempDir.toFile().path}/pre/delimiter/one ${tempDir.toFile().path}/pre/delimiter/two: ${tempDir.toFile().path}/post/delimiter/one ${tempDir.toFile().path}/post/delimiter/two"
)
every { mockDependenciesFileCollection.iterator() } returns (mutableListOf(dependenciesFile).iterator())
FlutterTaskHelper.getSourceFiles(project, mockFlutterTask)
verify {
project.files(
listOf(
"${tempDir.toFile().path}/post/delimiter/one",
"${tempDir.toFile().path}/post/delimiter/two"
)
)
}
verify { project.files("pubspec.yaml") }
}
@Test
fun `getOutputFiles returns files when dependenciesFile exists`(
@TempDir tempDir: Path
) {
val mockProjectFileCollection = mockk<ConfigurableFileCollection>(relaxed = true)
val mockDependenciesFileCollection = mockk<FileCollection>()
val project = mockk<Project>()
val mockFlutterTask = mockk<FlutterTask>()
every { project.files() } returns mockProjectFileCollection
every { project.files(any()) } returns mockProjectFileCollection
every { mockFlutterTask.intermediateDir } returns tempDir.toFile()
every { mockFlutterTask.getDependenciesFiles() } returns mockDependenciesFileCollection
val dependenciesFile =
tempDir
.resolve("${mockFlutterTask.intermediateDir}/flutter_build.d")
.toFile()
dependenciesFile.writeText(
" ${tempDir.toFile().path}/pre/delimiter/one ${tempDir.toFile().path}/pre/delimiter/two: ${tempDir.toFile().path}/post/delimiter/one ${tempDir.toFile().path}/post/delimiter/two"
)
every { mockDependenciesFileCollection.iterator() } returns (mutableListOf(dependenciesFile).iterator())
FlutterTaskHelper.getOutputFiles(project, mockFlutterTask)
verify {
project.files(
listOf(
"${tempDir.toFile().path}/pre/delimiter/one",
"${tempDir.toFile().path}/pre/delimiter/two"
)
)
}
verify(exactly = 0) { project.files("pubspec.yaml") }
}
}