diff --git a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy index 811de1f337..4ebbf4e3b2 100644 --- a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy +++ b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy @@ -11,6 +11,7 @@ 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 @@ -21,7 +22,6 @@ import org.gradle.api.file.Directory import java.nio.file.Paths import org.apache.tools.ant.taskdefs.condition.Os -import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.JavaVersion import org.gradle.api.Project @@ -30,14 +30,11 @@ 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.logging.LogLevel import org.gradle.api.tasks.Copy import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.OutputFiles -import org.gradle.api.tasks.Optional import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.bundling.Jar @@ -1459,190 +1456,6 @@ class FlutterPlugin implements Plugin { } -abstract class BaseFlutterTask extends DefaultTask { - - @Internal - File flutterRoot - - @Internal - File flutterExecutable - - @Input - String buildMode - - @Input - int minSdkVersion - - @Optional @Input - String localEngine - - @Optional @Input - String localEngineHost - - @Optional @Input - String localEngineSrcPath - - @Optional @Input - Boolean fastStart - - @Input - String targetPath - - @Optional @Input - Boolean verbose - - @Optional @Input - String[] fileSystemRoots - - @Optional @Input - String fileSystemScheme - - @Input - Boolean trackWidgetCreation - - @Optional @Input - List targetPlatformValues - - @Internal - File sourceDir - - @Internal - File intermediateDir - - @Optional @Input - String frontendServerStarterPath - - @Optional @Input - String extraFrontEndOptions - - @Optional @Input - String extraGenSnapshotOptions - - @Optional @Input - String splitDebugInfo - - @Optional @Input - Boolean treeShakeIcons - - @Optional @Input - Boolean dartObfuscation - - @Optional @Input - String dartDefines - - @Optional @Input - String codeSizeDirectory - - @Optional @Input - String performanceMeasurementFile - - @Optional @Input - Boolean deferredComponents - - @Optional @Input - Boolean validateDeferredComponents - - @Optional @Input - Boolean skipDependencyChecks - @Optional @Input - String flavor - - @OutputFiles - FileCollection getDependenciesFiles() { - FileCollection depfiles = project.files() - - // Includes all sources used in the flutter compilation. - depfiles += project.files("${intermediateDir}/flutter_build.d") - return depfiles - } - - void buildBundle() { - if (!sourceDir.isDirectory()) { - throw new GradleException("Invalid Flutter source directory: ${sourceDir}") - } - - intermediateDir.mkdirs() - - // Compute the rule name for flutter assemble. To speed up builds that contain - // multiple ABIs, the target name is used to communicate which ones are required - // rather than the TargetPlatform. This allows multiple builds to share the same - // cache. - String[] ruleNames - if (buildMode == "debug") { - ruleNames = ["debug_android_application"] - } else if (deferredComponents) { - ruleNames = targetPlatformValues.collect { "android_aot_deferred_components_bundle_${buildMode}_$it" } - } else { - ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" } - } - project.exec { - logging.captureStandardError(LogLevel.ERROR) - executable(flutterExecutable.absolutePath) - workingDir(sourceDir) - if (localEngine != null) { - args "--local-engine", localEngine - args "--local-engine-src-path", localEngineSrcPath - } - if (localEngineHost != null) { - args "--local-engine-host", localEngineHost - } - if (verbose) { - args "--verbose" - } else { - args "--quiet" - } - args("assemble") - args("--no-version-check") - args("--depfile", "${intermediateDir}/flutter_build.d") - args("--output", "${intermediateDir}") - if (performanceMeasurementFile != null) { - args("--performance-measurement-file=${performanceMeasurementFile}") - } - if (!fastStart || buildMode != "debug") { - args("-dTargetFile=${targetPath}") - } else { - args("-dTargetFile=${Paths.get(flutterRoot.absolutePath, "examples", "splash", "lib", "main.dart")}") - } - args("-dTargetPlatform=android") - args("-dBuildMode=${buildMode}") - if (trackWidgetCreation != null) { - args("-dTrackWidgetCreation=${trackWidgetCreation}") - } - if (splitDebugInfo != null) { - args("-dSplitDebugInfo=${splitDebugInfo}") - } - if (treeShakeIcons == true) { - args("-dTreeShakeIcons=true") - } - if (dartObfuscation == true) { - args("-dDartObfuscation=true") - } - if (dartDefines != null) { - args("--DartDefines=${dartDefines}") - } - if (codeSizeDirectory != null) { - args("-dCodeSizeDirectory=${codeSizeDirectory}") - } - if (flavor != null) { - args("-dFlavor=${flavor}") - } - if (extraGenSnapshotOptions != null) { - args("--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}") - } - if (frontendServerStarterPath != null) { - args("-dFrontendServerStarterPath=${frontendServerStarterPath}") - } - if (extraFrontEndOptions != null) { - args("--ExtraFrontEndOptions=${extraFrontEndOptions}") - } - args("-dAndroidArchs=${targetPlatformValues.join(' ')}") - args("-dMinSdkVersion=${minSdkVersion}") - args(ruleNames) - } - } - -} - class FlutterTask extends BaseFlutterTask { @OutputDirectory diff --git a/packages/flutter_tools/gradle/src/main/kotlin/BaseFlutterTask.kt b/packages/flutter_tools/gradle/src/main/kotlin/BaseFlutterTask.kt new file mode 100644 index 0000000000..7a23942f2e --- /dev/null +++ b/packages/flutter_tools/gradle/src/main/kotlin/BaseFlutterTask.kt @@ -0,0 +1,152 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +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() { + @Internal + var flutterRoot: File? = null + + @Internal + var flutterExecutable: File? = null + + @Input + var buildMode: String? = null + + @Input + var minSdkVersion: Int? = null + + @Optional + @Input + var localEngine: String? = null + + @Optional + @Input + var localEngineHost: String? = null + + @Optional + @Input + var localEngineSrcPath: String? = null + + @Optional + @Input + var fastStart: Boolean? = null + + @Input + var targetPath: String? = null + + @Optional + @Input + var verbose: Boolean? = null + + @Optional + @Input + var fileSystemRoots: Array? = null + + @Optional + @Input + var fileSystemScheme: String? = null + + @Input + var trackWidgetCreation: Boolean? = null + + @Optional + @Input + var targetPlatformValues: List? = null + + @Internal + var sourceDir: File? = null + + @Internal + var intermediateDir: File? = null + + @Optional + @Input + var frontendServerStarterPath: String? = null + + @Optional + @Input + var extraFrontEndOptions: String? = null + + @Optional + @Input + var extraGenSnapshotOptions: String? = null + + @Optional + @Input + var splitDebugInfo: String? = null + + @Optional + @Input + var treeShakeIcons: Boolean? = null + + @Optional + @Input + var dartObfuscation: Boolean? = null + + @Optional + @Input + var dartDefines: String? = null + + @Optional + @Input + var bundleSkSLPath: String? = null + + @Optional + @Input + var codeSizeDirectory: String? = null + + @Optional + @Input + var performanceMeasurementFile: String? = null + + @Optional + @Input + var deferredComponents: Boolean? = null + + @Optional + @Input + var validateDeferredComponents: Boolean? = null + + @Optional + @Input + var skipDependencyChecks: Boolean? = null + + @Optional + @Input + var flavor: String? = null + + /** + * Gets the dependency file(s) by calling [com.flutter.gradle.BaseFlutterTaskHelper.getDependenciesFiles]. + * + * @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 + } + + /** + * 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()) + } +} diff --git a/packages/flutter_tools/gradle/src/main/kotlin/BaseFlutterTaskHelper.kt b/packages/flutter_tools/gradle/src/main/kotlin/BaseFlutterTaskHelper.kt new file mode 100644 index 0000000000..b4f122f42a --- /dev/null +++ b/packages/flutter_tools/gradle/src/main/kotlin/BaseFlutterTaskHelper.kt @@ -0,0 +1,149 @@ +package com.flutter.gradle + +import androidx.annotation.VisibleForTesting +import org.gradle.api.Action +import org.gradle.api.GradleException +import org.gradle.api.file.FileCollection +import org.gradle.api.tasks.OutputFiles +import org.gradle.process.ExecSpec +import java.nio.file.Paths + +class BaseFlutterTaskHelper( + private var baseFlutterTask: BaseFlutterTask +) { + @VisibleForTesting + internal var gradleErrorMessage = "Invalid Flutter source directory: ${baseFlutterTask.sourceDir}" + + /** + * Gets the dependency file(s) that tracks the dependencies or input files used for a specific + * Flutter build step based on the current intermediate directory. + * + * @return the dependency file(s) based on the current intermediate directory. + */ + @OutputFiles + @VisibleForTesting + internal fun getDependenciesFiles(): FileCollection { + var depfiles: FileCollection = baseFlutterTask.project.files() + + // TODO(jesswon): During cleanup determine if .../flutter_build.d is ever a directory and refactor accordingly + // Includes all sources used in the flutter compilation. + depfiles += baseFlutterTask.project.files("${baseFlutterTask.intermediateDir}/flutter_build.d") + return depfiles + } + + /** + * Checks precondition to ensures sourceDir is not null and is a directory. Also checks + * if intermediateDir is valid valid and creates it (and parent directories if needed) if invalid. + * + * @throws GradleException if sourceDir is null or is not a directory + */ + @VisibleForTesting + internal fun checkPreConditions() { + if (baseFlutterTask.sourceDir == null || !baseFlutterTask.sourceDir!!.isDirectory) { + throw GradleException(gradleErrorMessage) + } + baseFlutterTask.intermediateDir!!.mkdirs() + } + + /** + * Computes the rule names for flutter assemble. To speed up builds that contain + * multiple ABIs, the target name is used to communicate which ones are required + * rather than the TargetPlatform. This allows multiple builds to share the same + * cache. + * + * @param baseFlutterTask is a BaseFlutterTask to access its properties + * @return the list of rule names for flutter assemble. + */ + @VisibleForTesting + internal fun generateRuleNames(baseFlutterTask: BaseFlutterTask): List { + val ruleNames: List = + when { + baseFlutterTask.buildMode == "debug" -> listOf("debug_android_application") + baseFlutterTask.deferredComponents!! -> + baseFlutterTask.targetPlatformValues!! + .map { + "android_aot_deferred_components_bundle_${baseFlutterTask.buildMode}_$it" + } + else -> baseFlutterTask.targetPlatformValues!!.map { "android_aot_bundle_${baseFlutterTask.buildMode}_$it" } + } + return ruleNames + } + + /** + * Creates and configures the build processes of an Android Flutter application to be executed. + * The configuration includes setting the executable to the Flutter command-line tool (Flutter CLI), + * setting the working directory to the Flutter project's source directory, adding command-line arguments and build rules + * to configure various build options. + * + * @return an Action of build processes and options to be executed. + */ + @VisibleForTesting + internal fun createExecSpecActionFromTask(): Action = + Action { + executable(baseFlutterTask.flutterExecutable!!.absolutePath) + workingDir(baseFlutterTask.sourceDir) + baseFlutterTask.localEngine?.let { + args("--local-engine", it) + args("--local-engine-src-path", baseFlutterTask.localEngineSrcPath) + } + baseFlutterTask.localEngineHost?.let { + args("--local-engine-host", it) + } + if (baseFlutterTask.verbose == true) { + args("--verbose") + } else { + args("--quiet") + } + args("assemble") + args("--no-version-check") + args("--depfile", "${baseFlutterTask.intermediateDir}/flutter_build.d") + args("--output", "${baseFlutterTask.intermediateDir}") + baseFlutterTask.performanceMeasurementFile?.let { + args("--performance-measurement-file=$it") + } + if (!baseFlutterTask.fastStart!! || baseFlutterTask.buildMode != "debug") { + args("-dTargetFile=${baseFlutterTask.targetPath}") + } else { + args("-dTargetFile=${Paths.get(baseFlutterTask.flutterRoot!!.absolutePath, "examples", "splash", "lib", "main.dart")}") + } + args("-dTargetPlatform=android") + args("-dBuildMode=${baseFlutterTask.buildMode}") + baseFlutterTask.trackWidgetCreation?.let { + args("-dTrackWidgetCreation=$it") + } + baseFlutterTask.splitDebugInfo?.let { + args("-dSplitDebugInfo=$it") + } + if (baseFlutterTask.treeShakeIcons == true) { + args("-dTreeShakeIcons=true") + } + if (baseFlutterTask.dartObfuscation == true) { + args("-dDartObfuscation=true") + } + baseFlutterTask.dartDefines?.let { + args("--DartDefines=$it") + } + baseFlutterTask.bundleSkSLPath?.let { + args("-dBundleSkSLPath=$it") + } + baseFlutterTask.codeSizeDirectory?.let { + args("-dCodeSizeDirectory=$it") + } + baseFlutterTask.flavor?.let { + args("-dFlavor=$it") + } + baseFlutterTask.extraGenSnapshotOptions?.let { + args("--ExtraGenSnapshotOptions=$it") + } + baseFlutterTask.frontendServerStarterPath?.let { + args("-dFrontendServerStarterPath=$it") + } + baseFlutterTask.extraFrontEndOptions?.let { + args("--ExtraFrontEndOptions=$it") + } + + args("-dAndroidArchs=${baseFlutterTask.targetPlatformValues!!.joinToString(" ")}") + args("-dMinSdkVersion=${baseFlutterTask.minSdkVersion}") + args(generateRuleNames(baseFlutterTask)) + } +} diff --git a/packages/flutter_tools/gradle/src/test/kotlin/BaseFlutterTaskHelperTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/BaseFlutterTaskHelperTest.kt new file mode 100644 index 0000000000..21204e5245 --- /dev/null +++ b/packages/flutter_tools/gradle/src/test/kotlin/BaseFlutterTaskHelperTest.kt @@ -0,0 +1,544 @@ +package com.flutter.gradle + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.gradle.api.GradleException +import org.gradle.process.ExecSpec +import org.gradle.process.ProcessForkOptions +import org.junit.jupiter.api.assertDoesNotThrow +import java.io.File +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class BaseFlutterTaskHelperTest { + object BaseFlutterTaskPropertiesTest { + internal const val LOCAL_ENGINE_TEST = "android_debug_arm64" + internal const val LOCAL_ENGINE_HOST_TEST = "host_debug" + internal const val DART_DEFINES_TEST = "ENVIRONMENT=development" + internal const val FLAVOR_TEST = "dev" + internal const val EXTRA_FRONTEND_OPTIONS_TEST = "--enable-asserts" + internal const val EXTRA_GEN_SNAPSHOT_OPTIONS_TEST = "--debugger" + internal const val TARGET_PLATFORM_VALUES_JOINED_LIST = "android linux" + val MIN_SDK_VERSION_TEST = DependencyVersionChecker.warnMinSdkVersion + + // Using File.separator to ensure all paths use platform-specific separators + internal val FLUTTER_ROOT_ABSOLUTE_PATH_TEST = "/path/to/flutter".replace("/", File.separator) + internal val FLUTTER_EXECUTABLE_ABSOLUTE_PATH_TEST = "/path/to/flutter/bin/flutter".replace("/", File.separator) + internal val LOCAL_ENGINE_SRC_PATH_TEST = "/path/to/flutter/engine/src".replace("/", File.separator) + internal val PERFORMANCE_MEASUREMENT_FILE_TEST = "/path/to/build/performance_file".replace("/", File.separator) + internal val FRONTEND_SERVER_STARTER_PATH_TEST = "/path/to/starter/script_file".replace("/", File.separator) + internal val SPLIT_DEBUG_INFO_TEST = "/path/to/build/debug_info_directory".replace("/", File.separator) + internal val CODE_SIZE_DIRECTORY_TEST = "/path/to/build/code_size_directory".replace("/", File.separator) + + internal val BUNDLE_SK_SL_PATH_TEST = "/path/to/custom/shaders".replace("/", File.separator) + internal val FLUTTER_TARGET_FILE_PATH = "/path/to/flutter/examples/splash/lib/main.dart".replace("/", File.separator) + internal val FLUTTER_TARGET_PATH = "/path/to/main.dart".replace("/", File.separator) + + internal val sourceDirTest = File("/path/to/working_directory".replace("/", File.separator)) + internal val flutterRootTest = File("/path/to/flutter".replace("/", File.separator)) + internal val flutterExecutableTest = File("/path/to/flutter/bin/flutter".replace("/", File.separator)) + internal val intermediateDirFileTest = File("/path/to/build/app/intermediates/flutter/release".replace("/", File.separator)) + internal val targetPlatformValuesList = listOf("android", "linux") + } + + @Test + fun `checkPreConditions throws a GradleException when sourceDir is null`() { + val baseFlutterTask = mockk() + every { baseFlutterTask.sourceDir } returns null + + val helper = BaseFlutterTaskHelper(baseFlutterTask) + + val gradleException = + assertFailsWith { helper.checkPreConditions() } + assert( + gradleException.message == + helper.gradleErrorMessage + ) + } + + @Test + fun `checkPreConditions throws a GradleException when sourceDir is not a directory`() { + val baseFlutterTask = mockk() + every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest + every { baseFlutterTask.sourceDir!!.isDirectory } returns false + + val helper = BaseFlutterTaskHelper(baseFlutterTask) + + val gradleException = + assertFailsWith { helper.checkPreConditions() } + assert( + gradleException.message == + helper.gradleErrorMessage + ) + } + + // TODO(jesswon): Add a test for intermediateDir is not valid during cleanup for handling NPEs. + @Test + fun `checkPreConditions does not throw a GradleException and intermediateDir is valid`() { + val baseFlutterTask = mockk() + + every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest + every { baseFlutterTask.sourceDir!!.isDirectory } returns true + + every { baseFlutterTask.intermediateDir } returns BaseFlutterTaskPropertiesTest.intermediateDirFileTest + // 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() } + } + + @Test + fun `generateRuleNames returns correct rule names when buildMode is debug`() { + val buildModeString = "debug" + + val baseFlutterTask = mockk() + // 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 + every { baseFlutterTask.buildMode } returns buildModeString + + val helper = BaseFlutterTaskHelper(baseFlutterTask) + val ruleNamesList = helper.generateRuleNames(baseFlutterTask) + + assertEquals(ruleNamesList, listOf("debug_android_application")) + } + + @Test + fun `generateRuleNames returns correct rule names when buildMode is not debug and deferredComponents is true`() { + val buildModeString = "release" + + val baseFlutterTask = mockk() + // 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 + every { baseFlutterTask.buildMode } returns buildModeString + every { baseFlutterTask.deferredComponents } returns true + every { baseFlutterTask.targetPlatformValues } returns BaseFlutterTaskPropertiesTest.targetPlatformValuesList + + val helper = BaseFlutterTaskHelper(baseFlutterTask) + val ruleNamesList = helper.generateRuleNames(baseFlutterTask) + + assertEquals( + ruleNamesList, + listOf( + "android_aot_deferred_components_bundle_release_android", + "android_aot_deferred_components_bundle_release_linux" + ) + ) + } + + @Test + fun `generateRuleNames returns correct rule names when buildMode is not debug and deferredComponents is false`() { + val buildModeString = "release" + + val baseFlutterTask = mockk() + // 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 + every { baseFlutterTask.buildMode } returns buildModeString + every { baseFlutterTask.deferredComponents } returns false + every { baseFlutterTask.targetPlatformValues } returns BaseFlutterTaskPropertiesTest.targetPlatformValuesList + + val helper = BaseFlutterTaskHelper(baseFlutterTask) + val ruleNamesList = helper.generateRuleNames(baseFlutterTask) + + assertEquals( + ruleNamesList, + listOf( + "android_aot_bundle_release_android", + "android_aot_bundle_release_linux" + ) + ) + } + + @Test + fun `createSpecActionFromTask creates the correct build configurations when properties are non-null`() { + val buildModeString = "debug" + + // Create necessary mocks. + val baseFlutterTask = mockk() + val mockExecSpec = mockk() + val mockProcessForkOptions = mockk() + + // 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() + + // Mock return values of properties. + every { baseFlutterTask.flutterExecutable } returns BaseFlutterTaskPropertiesTest.flutterExecutableTest + every { + baseFlutterTask.flutterExecutable!!.absolutePath + } returns BaseFlutterTaskPropertiesTest.FLUTTER_EXECUTABLE_ABSOLUTE_PATH_TEST + + 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()) } returns mockExecSpec + every { mockExecSpec.workingDir(any()) } returns mockProcessForkOptions + every { mockExecSpec.args(any(), any()) } returns mockExecSpec + every { mockExecSpec.args(any(), any()) } returns mockExecSpec + every { mockExecSpec.args(any()) } returns mockExecSpec + every { mockExecSpec.args(any>()) } returns mockExecSpec + + // Generate rule names for verification and can only be generated after buildMode is mocked. + val ruleNamesList: List = 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) } + } + + @Test + fun `createSpecActionFromTask creates the correct build configurations when properties are null`() { + val buildModeString = "debug" + + // Create necessary mocks. + val baseFlutterTask = mockk() + val mockExecSpec = mockk() + val mockProcessForkOptions = mockk() + + // 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() + + // Mock return values of properties. + every { baseFlutterTask.flutterExecutable } returns BaseFlutterTaskPropertiesTest.flutterExecutableTest + every { + baseFlutterTask.flutterExecutable!!.absolutePath + } returns BaseFlutterTaskPropertiesTest.FLUTTER_EXECUTABLE_ABSOLUTE_PATH_TEST + + every { baseFlutterTask.localEngine } returns null + every { baseFlutterTask.localEngineSrcPath } returns null + + every { baseFlutterTask.localEngineHost } returns null + every { baseFlutterTask.verbose } returns true + every { baseFlutterTask.intermediateDir } returns BaseFlutterTaskPropertiesTest.intermediateDirFileTest + every { baseFlutterTask.performanceMeasurementFile } returns null + + 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 null + every { baseFlutterTask.splitDebugInfo } returns null + every { baseFlutterTask.treeShakeIcons } returns null + + every { baseFlutterTask.dartObfuscation } returns null + every { baseFlutterTask.dartDefines } returns null + every { baseFlutterTask.bundleSkSLPath } returns null + + every { baseFlutterTask.codeSizeDirectory } returns null + every { baseFlutterTask.flavor } returns null + every { baseFlutterTask.extraGenSnapshotOptions } returns null + + every { baseFlutterTask.frontendServerStarterPath } returns null + every { baseFlutterTask.extraFrontEndOptions } returns null + + 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()) } returns mockExecSpec + every { mockExecSpec.workingDir(any()) } returns mockProcessForkOptions + every { mockExecSpec.args(any(), any()) } returns mockExecSpec + every { mockExecSpec.args(any(), any()) } returns mockExecSpec + every { mockExecSpec.args(any()) } returns mockExecSpec + every { mockExecSpec.args(any>()) } returns mockExecSpec + + // Generate rule names for verification and can only be generated after buildMode is mocked. + val ruleNamesList: List = 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("--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("-dTargetFile=${BaseFlutterTaskPropertiesTest.FLUTTER_TARGET_FILE_PATH}") } + verify { mockExecSpec.args("-dTargetPlatform=android") } + verify { mockExecSpec.args("-dBuildMode=$buildModeString") } + verify { mockExecSpec.args("-dAndroidArchs=${BaseFlutterTaskPropertiesTest.TARGET_PLATFORM_VALUES_JOINED_LIST}") } + verify { mockExecSpec.args("-dMinSdkVersion=${BaseFlutterTaskPropertiesTest.MIN_SDK_VERSION_TEST}") } + verify { mockExecSpec.args(ruleNamesList) } + } + + @Test + fun `createSpecActionFromTask creates the correct build configurations when verbose is false and fastStart is false`() { + val buildModeString = "debug" + + // Create necessary mocks. + val baseFlutterTask = mockk() + val mockExecSpec = mockk() + val mockProcessForkOptions = mockk() + + // 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() + + // Mock return values of properties. + every { baseFlutterTask.flutterExecutable } returns BaseFlutterTaskPropertiesTest.flutterExecutableTest + every { + baseFlutterTask.flutterExecutable!!.absolutePath + } returns BaseFlutterTaskPropertiesTest.FLUTTER_EXECUTABLE_ABSOLUTE_PATH_TEST + + 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 false + every { baseFlutterTask.intermediateDir } returns BaseFlutterTaskPropertiesTest.intermediateDirFileTest + every { baseFlutterTask.performanceMeasurementFile } returns BaseFlutterTaskPropertiesTest.PERFORMANCE_MEASUREMENT_FILE_TEST + + every { baseFlutterTask.fastStart } returns false + every { baseFlutterTask.buildMode } returns buildModeString + every { baseFlutterTask.targetPath } returns BaseFlutterTaskPropertiesTest.FLUTTER_TARGET_PATH + + 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()) } returns mockExecSpec + every { mockExecSpec.workingDir(any()) } returns mockProcessForkOptions + every { mockExecSpec.args(any(), any()) } returns mockExecSpec + every { mockExecSpec.args(any(), any()) } returns mockExecSpec + every { mockExecSpec.args(any()) } returns mockExecSpec + every { mockExecSpec.args(any>()) } returns mockExecSpec + + // Generate rule names for verification and can only be generated after buildMode is mocked. + val ruleNamesList: List = 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("--quiet") } + 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_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) } + } + + @Test + fun `createSpecActionFromTask creates the correct build configurations when fastStart is true and buildMode is not debug`() { + val buildModeString = "release" + + // Create necessary mocks. + val baseFlutterTask = mockk() + val mockExecSpec = mockk() + val mockProcessForkOptions = mockk() + + // 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() + + // Mock return values of properties. + every { baseFlutterTask.flutterExecutable } returns BaseFlutterTaskPropertiesTest.flutterExecutableTest + every { + baseFlutterTask.flutterExecutable!!.absolutePath + } returns BaseFlutterTaskPropertiesTest.FLUTTER_EXECUTABLE_ABSOLUTE_PATH_TEST + + 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.targetPath } returns BaseFlutterTaskPropertiesTest.FLUTTER_TARGET_PATH + + 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.deferredComponents } returns true + 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()) } returns mockExecSpec + every { mockExecSpec.workingDir(any()) } returns mockProcessForkOptions + every { mockExecSpec.args(any(), any()) } returns mockExecSpec + every { mockExecSpec.args(any(), any()) } returns mockExecSpec + every { mockExecSpec.args(any()) } returns mockExecSpec + every { mockExecSpec.args(any>()) } returns mockExecSpec + + // Generate rule names for verification and can only be generated after buildMode is mocked. + val ruleNamesList: List = 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_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) } + } +} diff --git a/packages/flutter_tools/gradle/src/test/kotlin/BaseFlutterTaskTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/BaseFlutterTaskTest.kt new file mode 100644 index 0000000000..bcf00ec474 --- /dev/null +++ b/packages/flutter_tools/gradle/src/test/kotlin/BaseFlutterTaskTest.kt @@ -0,0 +1,147 @@ +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() + val project = mockk() + val configFileCollection = mockk() + 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() + val mockExecSpec = mockk() + val mockProcessForkOptions = mockk() + + // 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()) } returns mockExecSpec + every { mockExecSpec.workingDir(any()) } returns mockProcessForkOptions + every { mockExecSpec.args(any(), any()) } returns mockExecSpec + every { mockExecSpec.args(any(), any()) } returns mockExecSpec + every { mockExecSpec.args(any()) } returns mockExecSpec + every { mockExecSpec.args(any>()) } returns mockExecSpec + + // Generate rule names for verification and can only be generated after buildMode is mocked. + val ruleNamesList: List = 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) } + } +}