blob: 146c7b89628a445d2f2b1d668a0d25dc04ea714c [file] [log] [blame]
package com.flutter.gradle
import com.android.build.api.dsl.ApplicationDefaultConfig
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.gradle.AbstractAppExtension
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.api.AndroidSourceDirectorySet
import com.android.build.gradle.internal.core.InternalBaseVariant
import com.android.build.gradle.tasks.MergeSourceSetFolders
import com.android.build.gradle.tasks.ProcessAndroidResources
import com.flutter.gradle.tasks.FlutterTask
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.slot
import io.mockk.verify
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.Directory
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.plugin.extraProperties
import org.junit.jupiter.api.Assertions.fail
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Path
import kotlin.io.path.writeText
import kotlin.test.Test
class FlutterPluginTest {
@Test
fun `FlutterPlugin apply() adds expected tasks`(
@TempDir tempDir: Path
) {
val projectDir = tempDir.resolve("project-dir").resolve("android").resolve("app")
projectDir.toFile().mkdirs()
val settingsFile = projectDir.parent.resolve("settings.gradle")
settingsFile.writeText("empty for now")
val fakeFlutterSdkDir = tempDir.resolve("fake-flutter-sdk")
fakeFlutterSdkDir.toFile().mkdirs()
val fakeCacheDir = fakeFlutterSdkDir.resolve("bin").resolve("cache")
fakeCacheDir.toFile().mkdirs()
val fakeEngineStampFile = fakeCacheDir.resolve("engine.stamp")
fakeEngineStampFile.writeText(FAKE_ENGINE_STAMP)
val fakeEngineRealmFile = fakeCacheDir.resolve("engine.realm")
fakeEngineRealmFile.writeText(FAKE_ENGINE_REALM)
val project = mockk<Project>(relaxed = true)
val mockAbstractAppExtension = mockk<AbstractAppExtension>(relaxed = true)
every { project.extensions.findByType(AbstractAppExtension::class.java) } returns mockAbstractAppExtension
val mockAndroidComponentsExtension = mockk<AndroidComponentsExtension<*, *, *>>(relaxed = true)
every { project.extensions.getByType(AndroidComponentsExtension::class.java) } returns mockAndroidComponentsExtension
every { mockAndroidComponentsExtension.selector() } returns
mockk {
every { all() } returns mockk()
}
every { project.extensions.getByType(AbstractAppExtension::class.java) } returns mockAbstractAppExtension
every { project.extensions.findByName("android") } returns mockAbstractAppExtension
every { project.projectDir } returns projectDir.toFile()
every { project.findProperty("flutter.sdk") } returns fakeFlutterSdkDir.toString()
every { project.file(fakeFlutterSdkDir.toString()) } returns fakeFlutterSdkDir.toFile()
val flutterExtension = FlutterExtension()
every { project.extensions.create("flutter", any<Class<*>>()) } returns flutterExtension
every { project.extensions.findByType(FlutterExtension::class.java) } returns flutterExtension
val mockBaseExtension = mockk<BaseExtension>(relaxed = true)
every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension
val mockApplicationExtension = mockk<ApplicationExtension>(relaxed = true)
every { project.extensions.findByType(ApplicationExtension::class.java) } returns mockApplicationExtension
val mockApplicationDefaultConfig = mockk<ApplicationDefaultConfig>(relaxed = true)
every { mockApplicationExtension.defaultConfig } returns mockApplicationDefaultConfig
every { project.rootProject } returns project
every { project.state.failure } returns null
val mockDirectory = mockk<Directory>(relaxed = true)
every { project.layout.buildDirectory.get() } returns mockDirectory
val mockAndroidSourceSet = mockk<com.android.build.gradle.api.AndroidSourceSet>(relaxed = true)
val mockAndroidSourceDirectorySet = mockk<AndroidSourceDirectorySet>(relaxed = true)
every { mockAndroidSourceSet.jniLibs.srcDir(any()) } returns mockAndroidSourceDirectorySet
every { mockAbstractAppExtension.sourceSets.getByName("main") } returns mockAndroidSourceSet
// mock return of NativePluginLoaderReflectionBridge.getPlugins
mockkObject(NativePluginLoaderReflectionBridge)
every { NativePluginLoaderReflectionBridge.getPlugins(any(), any()) } returns
listOf()
// mock method calls that are invoked by the args to NativePluginLoaderReflectionBridge
every { project.extraProperties } returns mockk()
every { project.file(flutterExtension.source!!) } returns mockk()
val flutterPlugin = FlutterPlugin()
flutterPlugin.apply(project)
verify { project.tasks.register("generateLockfiles", any()) }
verify { project.tasks.register("javaVersion", any()) }
verify { project.tasks.register("printBuildVariants", any()) }
}
@Test
fun `copyFlutterAssets task sets filePermissions correctly`(
@TempDir tempDir: Path
) {
val projectDir = tempDir.resolve("project-dir").resolve("android").resolve("app")
projectDir.toFile().mkdirs()
val settingsFile = projectDir.parent.resolve("settings.gradle")
settingsFile.writeText("empty for now")
val fakeFlutterSdkDir = tempDir.resolve("fake-flutter-sdk")
fakeFlutterSdkDir.toFile().mkdirs()
val fakeCacheDir = fakeFlutterSdkDir.resolve("bin").resolve("cache")
fakeCacheDir.toFile().mkdirs()
val fakeEngineStampFile = fakeCacheDir.resolve("engine.stamp")
fakeEngineStampFile.writeText(FAKE_ENGINE_STAMP)
val fakeEngineRealmFile = fakeCacheDir.resolve("engine.realm")
fakeEngineRealmFile.writeText(FAKE_ENGINE_REALM)
val project = mockk<Project>(relaxed = true)
val mockAbstractAppExtension = mockk<AbstractAppExtension>(relaxed = true)
every { project.extensions.findByType(AbstractAppExtension::class.java) } returns mockAbstractAppExtension
every { project.extensions.getByType(AbstractAppExtension::class.java) } returns mockAbstractAppExtension
every { project.extensions.findByName("android") } returns mockAbstractAppExtension
val mockAndroidComponentsExtension = mockk<AndroidComponentsExtension<*, *, *>>(relaxed = true)
every { project.extensions.getByType(AndroidComponentsExtension::class.java) } returns mockAndroidComponentsExtension
every { mockAndroidComponentsExtension.selector() } returns
mockk {
every { all() } returns mockk()
}
every { project.projectDir } returns projectDir.toFile()
every { project.findProperty("flutter.sdk") } returns fakeFlutterSdkDir.toString()
every { project.file(fakeFlutterSdkDir.toString()) } returns fakeFlutterSdkDir.toFile()
val flutterExtension = FlutterExtension()
every { project.extensions.create("flutter", any<Class<*>>()) } returns flutterExtension
every { project.extensions.findByType(FlutterExtension::class.java) } returns flutterExtension
val mockBaseExtension = mockk<BaseExtension>(relaxed = true)
every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension
val mockApplicationExtension = mockk<ApplicationExtension>(relaxed = true)
every { project.extensions.findByType(ApplicationExtension::class.java) } returns mockApplicationExtension
val mockApplicationDefaultConfig = mockk<ApplicationDefaultConfig>(relaxed = true)
every { mockApplicationExtension.defaultConfig } returns mockApplicationDefaultConfig
every { project.rootProject } returns project
every { project.state.failure } returns null
val mockDirectory = mockk<Directory>(relaxed = true)
every { project.layout.buildDirectory.get() } returns mockDirectory
val mockAndroidSourceSet = mockk<com.android.build.gradle.api.AndroidSourceSet>(relaxed = true)
val mockAndroidSourceDirectorySet = mockk<AndroidSourceDirectorySet>(relaxed = true)
every { mockAndroidSourceSet.jniLibs.srcDir(any()) } returns mockAndroidSourceDirectorySet
every { mockAbstractAppExtension.sourceSets.getByName("main") } returns mockAndroidSourceSet
// mock return of NativePluginLoaderReflectionBridge.getPlugins
mockkObject(NativePluginLoaderReflectionBridge)
every { NativePluginLoaderReflectionBridge.getPlugins(any(), any()) } returns
listOf()
// mock method calls that are invoked by the args to NativePluginLoaderReflectionBridge
every { project.extraProperties } returns mockk()
every { project.file(flutterExtension.source!!) } returns mockk()
// Set up the task container and our task capture
val taskContainer = mockk<TaskContainer>(relaxed = true)
every { project.tasks } returns taskContainer
val copyTaskActionCaptor = slot<Action<Copy>>()
val copyTask = mockk<Copy>(relaxed = true)
val mockVariant = mockk<com.android.build.gradle.api.ApplicationVariant>(relaxed = true)
every { mockVariant.name } returns "debug"
every { mockVariant.buildType.name } returns "debug"
every { mockVariant.flavorName } returns ""
val mergedFlavor = mockk<InternalBaseVariant.MergedFlavor>(relaxed = true)
every { mockVariant.mergedFlavor } returns mergedFlavor
val apiLevel = mockk<com.android.builder.model.ApiVersion>(relaxed = true)
every { apiLevel.apiLevel } returns 21
every { mergedFlavor.minSdkVersion } returns apiLevel
val variantOutput = mockk<com.android.build.gradle.api.BaseVariantOutput>(relaxed = true)
val outputsIterator = mockk<MutableIterator<com.android.build.gradle.api.BaseVariantOutput>>()
every { outputsIterator.hasNext() } returns true andThen false
every { outputsIterator.next() } returns variantOutput
val variantOutputCollection = mockk<org.gradle.api.DomainObjectCollection<com.android.build.gradle.api.BaseVariantOutput>>()
every { variantOutputCollection.iterator() } returns outputsIterator
every { mockVariant.outputs } returns variantOutputCollection
val processResourcesProvider = mockk<TaskProvider<ProcessAndroidResources>>(relaxed = true)
every { processResourcesProvider.hint(ProcessAndroidResources::class).get() } returns mockk<ProcessAndroidResources>(relaxed = true)
every { variantOutput.processResourcesProvider } returns processResourcesProvider
val assembleTask = mockk<Task>(relaxed = true)
val assembleTaskProvider = mockk<TaskProvider<Task>>(relaxed = true)
every { assembleTaskProvider.get() } returns assembleTask
every { mockVariant.assembleProvider } returns assembleTaskProvider
val variants = listOf(mockVariant)
val variantsIterator = mockk<MutableIterator<com.android.build.gradle.api.ApplicationVariant>>()
every { variantsIterator.hasNext() } returns true andThen false
every { variantsIterator.next() } returns mockVariant
val variantCollection = mockk<org.gradle.api.DomainObjectSet<com.android.build.gradle.api.ApplicationVariant>>()
every { mockAbstractAppExtension.applicationVariants } returns variantCollection
every { variantCollection.iterator() } returns variantsIterator
every {
variantCollection.configureEach(any<Action<com.android.build.gradle.api.ApplicationVariant>>())
} answers {
variants.forEach { firstArg<Action<com.android.build.gradle.api.ApplicationVariant>>().execute(it) }
}
every { mockVariant.mergeAssetsProvider.hint(MergeSourceSetFolders::class).get() } returns
mockk<MergeSourceSetFolders>(relaxed = true)
val flutterTask = mockk<FlutterTask>(relaxed = true)
val copySpec = mockk<org.gradle.api.file.CopySpec>(relaxed = true)
every {
(flutterTask).assets
} returns copySpec
val flutterTaskProvider = mockk<TaskProvider<FlutterTask>>(relaxed = true)
every {
flutterTaskProvider.hint(FlutterTask::class).get()
} returns flutterTask
every {
taskContainer.register(
match { it.contains("compileFlutterBuild") },
any<Class<FlutterTask>>(),
any()
)
} answers {
flutterTaskProvider
}
// Actual task that should be captured to test if permissions have been set
val mockCopyTaskProvider = mockk<TaskProvider<Copy>>(relaxed = true)
every { mockCopyTaskProvider.hint(Copy::class).get() } returns copyTask
every {
taskContainer.register(
match { it.startsWith("copyFlutterAssets") },
eq(Copy::class.java),
capture(copyTaskActionCaptor)
)
} answers {
mockCopyTaskProvider
}
val mockJarTaskProvider = mockk<TaskProvider<org.gradle.api.tasks.bundling.Jar>>(relaxed = true)
every { mockJarTaskProvider.hint(org.gradle.api.tasks.bundling.Jar::class).get() } returns
mockk<org.gradle.api.tasks.bundling.Jar>(relaxed = true)
every {
taskContainer.register(
match { it.contains("packJniLibs") },
eq(org.gradle.api.tasks.bundling.Jar::class.java),
any()
)
} answers {
mockJarTaskProvider
}
val mockTaskProvider = mockk<TaskProvider<Task>>(relaxed = true)
every { mockTaskProvider.hint(Task::class).get() } returns mockk<Task>(relaxed = true)
every {
taskContainer.named(any<String>())
} returns mockTaskProvider
val flutterPlugin = FlutterPlugin()
flutterPlugin.apply(project)
copyTaskActionCaptor.captured.execute(copyTask)
val filePermissionsActionCaptor = slot<Action<org.gradle.api.file.ConfigurableFilePermissions>>()
verify {
copyTask.filePermissions(capture(filePermissionsActionCaptor))
}
if (filePermissionsActionCaptor.isCaptured) {
val mockFilePermissionSet = mockk<org.gradle.api.file.ConfigurableFilePermissions>(relaxed = true)
filePermissionsActionCaptor.captured.execute(mockFilePermissionSet)
val userPermissionsActionCaptor = slot<Action<org.gradle.api.file.ConfigurableUserClassFilePermissions>>()
verify {
mockFilePermissionSet.user(capture(userPermissionsActionCaptor))
}
if (userPermissionsActionCaptor.isCaptured) {
val mockUserPermission = mockk<org.gradle.api.file.ConfigurableUserClassFilePermissions>(relaxed = true)
userPermissionsActionCaptor.captured.execute(mockUserPermission)
verify {
mockUserPermission.read = true
mockUserPermission.write = true
}
} else {
fail("User permissions configuration action was not captured")
}
} else {
fail("FilePermissions configuration action was not captured")
}
}
companion object {
const val FAKE_ENGINE_STAMP = "901b0f1afe77c3555abee7b86a26aaa37f131379"
const val FAKE_ENGINE_REALM = "made_up_realm"
}
}