-
Notifications
You must be signed in to change notification settings - Fork 685
dataconnect: add gradle plugin that enables sharing code from src/test to src/androidTest #8098
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
982ce08
dataconnect: add gradle plugin that enables sharing testutil code in …
dconeybe 6d037a6
CopySharedWithAndroidTestFiles.kt: removed logic to delete output dir…
dconeybe ffdf842
CopySharedWithAndroidTestFiles.kt: improve search for the annotation …
dconeybe 813b0ba
CopySharedWithAndroidTestFiles.kt: oops!
dconeybe 19e4464
CopySharedWithAndroidTestFiles.kt: emit relative paths into generated…
dconeybe ceab1d3
Empty commit to trigger github actions
dconeybe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
139 changes: 139 additions & 0 deletions
139
...otlin/com/google/firebase/dataconnect/gradle/sharedtest/CopySharedWithAndroidTestFiles.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| /* | ||
| * Copyright 2026 Google LLC | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package com.google.firebase.dataconnect.gradle.sharedtest | ||
|
|
||
| import org.gradle.api.DefaultTask | ||
| import org.gradle.api.file.DirectoryProperty | ||
| import org.gradle.api.logging.Logger | ||
| import org.gradle.api.tasks.InputDirectory | ||
| import org.gradle.api.tasks.Internal | ||
| import org.gradle.api.tasks.Optional | ||
| import org.gradle.api.tasks.OutputDirectory | ||
| import org.gradle.api.tasks.TaskAction | ||
| import java.io.File | ||
|
|
||
| /** | ||
| * A Gradle task that copies Kotlin source files annotated with `@file:SharedWithAndroidTest` from | ||
| * an input directory to an output directory. | ||
| * | ||
| * This driving motivation behind creating this task is to share test utilities or fixtures between | ||
| * unit tests and Android instrumentation tests by copying the annotated files into the appropriate | ||
| * source set before compilation. | ||
| */ | ||
| abstract class CopySharedWithAndroidTestFiles : DefaultTask() { | ||
|
|
||
| @get:Internal abstract val inputBaseDirectory: DirectoryProperty | ||
|
|
||
| @get:InputDirectory abstract val inputDirectory: DirectoryProperty | ||
|
|
||
| @get:OutputDirectory abstract val outputDirectory: DirectoryProperty | ||
|
|
||
| @TaskAction | ||
| fun run() { | ||
| val inputBaseDirectory: File = inputBaseDirectory.get().asFile | ||
| val inputDirectory: File = inputDirectory.get().asFile | ||
| val outputDirectory: File = outputDirectory.get().asFile | ||
|
|
||
| logger.info("inputBaseDirectory={}", inputBaseDirectory.absolutePath) | ||
| logger.info("inputDirectory={}", inputDirectory.absolutePath) | ||
|
dconeybe marked this conversation as resolved.
|
||
| logger.info("outputDirectory={}", outputDirectory.absolutePath) | ||
|
|
||
| copyAnnotatedFiles( | ||
| srcBaseDir = inputBaseDirectory, | ||
| srcDir = inputDirectory, | ||
| destDir = outputDirectory, | ||
| taskPath = path, | ||
| logger = logger, | ||
| ) | ||
| } | ||
|
dconeybe marked this conversation as resolved.
|
||
| } | ||
|
|
||
| /** | ||
| * Recursively searches through [srcDir] for Kotlin source files (`.kt`) that contain the target | ||
| * annotation and copies them to [destDir], maintaining their relative directory structure. | ||
| * | ||
| * @param srcBaseDir The root directory to use when reporting file system paths in the generated | ||
| * files, rather than their absolute paths, to avoid defeating build caches; this **MUST** be a | ||
| * parent directory of [srcDir]. | ||
| * @param srcDir The root directory to search for annotated Kotlin files. | ||
| * @param destDir The root directory where annotated files should be copied. | ||
| * @param taskPath The full Gradle path of the task performing the copy. | ||
| * @param logger The Gradle logger to use for logging the copy operations. | ||
| */ | ||
| private fun copyAnnotatedFiles(srcBaseDir: File, srcDir: File, destDir: File, taskPath: String, logger: Logger) { | ||
| if (!srcDir.exists()) { | ||
| logger.info("source directory was not found; no files copied") | ||
| return | ||
| } | ||
| if (!srcDir.isDirectory) { | ||
| logger.info("source directory is not a directory; no files copied") | ||
| return | ||
| } | ||
|
|
||
| val srcFilesToCopy = srcDir | ||
| .walk() | ||
| .filter { it.isFile && it.extension == "kt" && it.hasALineEqualToOneOf(sharedWithAndroidTestAnnotationLines) } | ||
| .toList() | ||
|
|
||
| logger.info( | ||
| "Found {} files in {} that have a line equal to one of: {}", | ||
| srcFilesToCopy.size, | ||
| srcDir, | ||
| sharedWithAndroidTestAnnotationLines.joinToString { "\"$it\"" } | ||
| ) | ||
|
|
||
| if (srcFilesToCopy.isEmpty()) { | ||
| logger.info("No files found to copy; no files copied") | ||
| return | ||
| } | ||
|
|
||
| srcFilesToCopy.forEachIndexed { fileIndex, srcFile -> | ||
| val relativePath = srcFile.relativeTo(srcDir) | ||
| val destFile = File(destDir, relativePath.path) | ||
| logger.info("Copying file {}/{}: {} to {}", (fileIndex+1), srcFilesToCopy.size, srcFile, destFile) | ||
|
|
||
| destFile.parentFile?.mkdirs() | ||
|
|
||
| destFile.printWriter().use { | ||
| val srcFileRelative = srcFile.absoluteFile.relativeTo(srcBaseDir.absoluteFile) | ||
| it.println("// Generated from: $srcFileRelative") | ||
| it.println("// Generated by Gradle task: $taskPath") | ||
| it.println("//") | ||
| it.println("// WARNING: This file is generated!") | ||
| it.println("// Any changes made to this file will eventually be overwritten by the build.") | ||
| it.println("// If changes are desired, make them in the original file instead.") | ||
| it.println() | ||
|
|
||
| srcFile.useLines { lines -> | ||
| lines.forEach { line -> | ||
| it.println(line) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private val sharedWithAndroidTestAnnotationLines = listOf( | ||
| "@file:SharedWithAndroidTest", | ||
| "@file:com.google.firebase.dataconnect.testutil.SharedWithAndroidTest", | ||
| ) | ||
|
|
||
| private fun File.hasALineEqualToOneOf(desiredLines: Collection<String>): Boolean = | ||
| useLines { lines -> | ||
| lines.any { | ||
| it.trim() in desiredLines | ||
| } | ||
| } | ||
74 changes: 74 additions & 0 deletions
74
...n/kotlin/com/google/firebase/dataconnect/gradle/sharedtest/SharedWithAndroidTestPlugin.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| /* | ||
| * Copyright 2026 Google LLC | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package com.google.firebase.dataconnect.gradle.sharedtest | ||
|
|
||
| import com.android.build.api.variant.AndroidComponentsExtension | ||
| import com.android.build.api.variant.HasAndroidTest | ||
| import com.android.build.api.variant.Variant | ||
| import java.util.Locale | ||
| import org.gradle.api.Plugin | ||
| import org.gradle.api.Project | ||
| import org.gradle.api.plugins.ExtensionContainer | ||
| import org.gradle.api.tasks.TaskContainer | ||
| import org.gradle.kotlin.dsl.register | ||
|
|
||
| /** | ||
| * A Gradle plugin that, when applied, makes all files in `src/test/kotlin` that are annotated with | ||
| * `@file:SharedWithAndroidTest` available to code in `src/androidTest/kotlin`, enabling test | ||
| * utilities written for unit tests to also be available to integration tests. | ||
| * | ||
| * This is achieved by adding a "code generation" step to the `androidTest` target which simply | ||
| * copies the appropriately-annotated files into the "generated code" directory. | ||
| * | ||
| * To apply this plugin to an Android application or library, simply register the plugin alongside | ||
| * other Gradle plugins in `build.gradle.kts`: | ||
| * | ||
| * ``` | ||
| * plugins { | ||
| * // other plugins | ||
| * id("com.google.firebase.dataconnect.sharedtest") | ||
| * } | ||
| * ``` | ||
| */ | ||
| @Suppress("unused") | ||
| abstract class SharedWithAndroidTestPlugin : Plugin<Project> { | ||
| override fun apply(project: Project) = applyPlugin(project.extensions, project.tasks) | ||
| } | ||
|
|
||
| private fun applyPlugin(extensions: ExtensionContainer, tasks: TaskContainer) { | ||
| val androidComponents = extensions.getByType(AndroidComponentsExtension::class.java) | ||
| androidComponents.onVariants { variant -> handleVariant(variant, tasks) } | ||
| } | ||
|
|
||
| private fun handleVariant(variant: Variant, tasks: TaskContainer) { | ||
| val androidTest = (variant as? HasAndroidTest)?.androidTest ?: return | ||
| val variantNameTitleCase = variant.name.replaceFirstChar { it.titlecase(Locale.US) } | ||
|
|
||
| val task = | ||
| tasks.register<CopySharedWithAndroidTestFiles>( | ||
| "copy${variantNameTitleCase}SharedWithAndroidTestFiles" | ||
| ) { | ||
| val projectDirectory = project.layout.projectDirectory | ||
| inputBaseDirectory.set(projectDirectory) | ||
| inputDirectory.set(projectDirectory.dir("src/test/kotlin")) | ||
| } | ||
|
|
||
| androidTest.sources.java!!.addGeneratedSourceDirectory( | ||
| task, | ||
| CopySharedWithAndroidTestFiles::outputDirectory | ||
| ) | ||
| } | ||
|
dconeybe marked this conversation as resolved.
|
||
35 changes: 35 additions & 0 deletions
35
...connect/src/test/kotlin/com/google/firebase/dataconnect/testutil/SharedWithAndroidTest.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| /* | ||
| * Copyright 2026 Google LLC | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| @file:SharedWithAndroidTest | ||
|
|
||
| package com.google.firebase.dataconnect.testutil | ||
|
|
||
| /** | ||
| * Annotation used to mark files that should be shared with the androidTest source set. | ||
| * | ||
| * This annotation is processed by the CopySharedWithAndroidTestFiles Gradle task. For performance | ||
| * and simplicity, the task performs a rudimentary string-based check on the file's contents. To be | ||
| * recognized, a line in the source file must, when trimmed, exactly equal either: | ||
| * - `@file:SharedWithAndroidTest` | ||
| * - `@file:com.google.firebase.dataconnect.testutil.SharedWithAndroidTest` | ||
| * | ||
| * Notably, "grouped syntax" like `@file:[JvmName("MyFile") SharedWithAndroidTest]` is NOT supported | ||
| * and will not be recognized by the task. | ||
| */ | ||
| @Target(AnnotationTarget.FILE) | ||
| @Retention(AnnotationRetention.SOURCE) | ||
| internal annotation class SharedWithAndroidTest |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.