Skip to content
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

basic Gradle integration test scaffolding #789

Merged
merged 14 commits into from
Dec 6, 2023

Conversation

RBusarow
Copy link
Collaborator

No description provided.

@RBusarow RBusarow force-pushed the rick/gradle-integration-test-support branch 4 times, most recently from 4632e6f to 8584e97 Compare November 20, 2023 23:23
@RBusarow RBusarow marked this pull request as ready for review November 21, 2023 23:10
@RBusarow RBusarow force-pushed the rick/gradle-integration-test-support branch from 8584e97 to 095e67a Compare November 28, 2023 04:45
Copy link
Member

@JoelWilcox JoelWilcox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review

.github/workflows/ci.yml Outdated Show resolved Hide resolved
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.gradle.plugins.ide.idea.model.IdeaModel

abstract class IntegrationTestsConventionPlugin : Plugin<Project> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a doc comment for this?

Also is there a more specific name that works here? Right now it's a bit confusing having an integrationTest task and the integration-tests module that are both unrelated to each other. Maybe this could be pluginIntegrationTest, gradleIntegrationTest, etc for the task name to differentiate it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Comment on lines 15 to 22
when (target.path) {
":annotations",
":annotations-optional",
":compiler",
":compiler-api",
":compiler-utils",
":gradle-plugin",
-> target.setBuildDir()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conditionally applying by target path/module like this feels unexpected instead of setting the build dir for anyone that has chosen to apply this plugin. What do you think about having a separate plugin to handle the case for simple lib modules that only want the above (kotlin-jvm, ktlint) plugins applied? The simpler plugin could be something like MinimalLibraryPlugin or the other direction where the library plugin with this build dir setup logic could be named around what it's doing differently to indicate which modules should use it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!


internal fun Project.isInMainAnvilBuild() = rootProject.name == "anvil"

internal fun Project.isInDelegateBuild() = rootProject.name == "delegate"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some high level documentation somewhere around the main build vs delegate build, why we have both, etc?

@RBusarow RBusarow force-pushed the rick/gradle-integration-test-support branch 3 times, most recently from 65704b8 to 078e8ea Compare December 1, 2023 21:05
target.extras["skipDokka"] = true
}

target.rootProject.tasks.named("publishToBuildM2").dependsOn(publishToBuildM2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I reading correctly that this is making sure when we run publish at the root level, that all subprojects applying the plugin get their publish task executed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. The publish tasks don't chain together the way that build tasks do, so this is a pretty common catch-all.

override fun beforeApply(target: Project) {
target.plugins.apply(target.libs.plugins.kotlin.jvm.pluginId)

target.plugins.apply(PublishConventionPlugin::class.java)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep publishing separate and have libs apply it as needed? Not every library necessarily needs to be published, unless we expect non-published library modules to use a different plugin

For example, the use-case I'm thinking of is that we create a new module like compiler-utils-2 that is only used internally by compiler-utils and does not need to be exposed to anyone else.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!


target.logVersionInfo()

if (target.isInMainAnvilBuild() || target.isInDelegateBuild()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the other options beyond main build and delegate build here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once upon a time...

I forgot to clean this up. :)

@RBusarow RBusarow force-pushed the rick/gradle-integration-test-support branch 5 times, most recently from 91fae50 to c6b71f3 Compare December 4, 2023 22:17
@RBusarow RBusarow force-pushed the rick/gradle-integration-test-support branch from c6b71f3 to f28bd56 Compare December 5, 2023 20:02
Comment on lines +34 to +82
val newRequests = oldRequests.map { request ->

val originalSplit = request.args.splitTaskArgs()

val taskPaths = originalSplit.mapTo(mutableSetOf()) { it.first() }

val includedProjects = gradle.includedBuilds
.asSequence()
.filterIsInstance<IncludedBuildImpl>()
.flatMap { impl ->

val defaultIncludedBuild = impl.target as DefaultIncludedBuild

defaultIncludedBuild.mutableModel.projectRegistry.allProjects
}

val newSplit = originalSplit.flatMap { taskWithArgs ->

val taskName = taskWithArgs.first()

if (taskName.startsWith(':')) {
// qualified task names aren't propagated to included builds
return@flatMap listOf(taskWithArgs)
}

// Don't propagate help tasks
if (taskName == "help") return@flatMap listOf(taskWithArgs)

val included = includedProjects.mapNotNull { includedProject ->

val includedPath = "${includedProject.identityPath}:$taskName"

// Don't include tasks that are already in the task graph
if (taskPaths.contains(includedPath)) return@mapNotNull null

includedProject.tasks.namedOrNull(taskName) ?: return@mapNotNull null

target.logger.quiet("The task $taskName will delegate to $includedPath")

buildList<String> {
add(includedPath)
addAll(taskWithArgs.subList(1, taskWithArgs.size))
}
}

buildList {

// Looks to see if any project in this build has a task with this name.
val resolvedInRootBuild = target.allprojects
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some tests around some of this task finagling logic? Could be a follow-up PR in the interest of not further delaying the rest of the changes

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

private fun configureTests(target: Project) {
target.tasks.withType(Test::class.java).configureEach { task ->

task.maxParallelForks = Runtime.getRuntime().availableProcessors()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like previously we only did this for local machines. Is there any downside to now setting it on CI?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. It's a pretty common setting.

task.maxParallelForks = Runtime.getRuntime().availableProcessors()

task.testLogging { logging ->
logging.events("passed", "skipped", "failed")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we not include the 'standardOut', 'standardError' events now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything that we actually need to see (for a failed test) will still be included in the output as the exception/stacktrace. There's so much noise in the console output that it becomes difficult to find an actual error when you need to.

@RBusarow RBusarow merged commit 9bfb9a1 into main Dec 6, 2023
21 checks passed
@RBusarow RBusarow deleted the rick/gradle-integration-test-support branch December 6, 2023 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants