Skip to content

Commit a249dd3

Browse files
bric3devflow.devflow-routing-intake
andauthored
Cover muzzle plugin by tests (#10643)
chore: New MuzzlePlugin tests chore: Factors out the task planification out of muzzle plugin for easier testing feat: Easier testing for buildSrc tests chore: New plan tests chore: First full integration test chore: New muzzle integration tests chore: New muzzle integration tests about reacting to environment chore: Add language for easier code review test: Rename test: Uses task outcome test: Simplify some test test: Add new avoidance tests, simplify hasRelevantTask check test: Adds basic up-to-date testcase test: Refactors maven fixture to support more usecases test: Ensures case is not up-to-date when classpath changes test: Adds some muzzle plugin utils test: adds muzzle directive tests test: adds muzzle repo tests test: adds assertInverse functional test test: Use AssertJ test: check_build_src do not the build job to run before fix: Properly run fail directives Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
1 parent 8995a31 commit a249dd3

24 files changed

Lines changed: 2786 additions & 77 deletions

.gitlab-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ test_published_artifacts:
464464

465465
check_build_src:
466466
extends: .check_job
467+
needs: []
467468
variables:
468469
GRADLE_TARGET: ":buildSrc:build"
469470

buildSrc/build.gradle.kts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ dependencies {
7878

7979
implementation("org.eclipse.aether", "aether-connector-basic", "1.1.0")
8080
implementation("org.eclipse.aether", "aether-transport-http", "1.1.0")
81+
implementation("org.eclipse.aether", "aether-transport-file", "1.1.0")
8182
implementation("org.apache.maven", "maven-aether-provider", "3.3.9")
8283

8384
implementation("com.github.zafarkhaja:java-semver:0.10.2")
@@ -103,9 +104,12 @@ testing {
103104
@Suppress("UnstableApiUsage")
104105
suites {
105106
val test by getting(JvmTestSuite::class) {
107+
dependencies {
108+
implementation(libs.assertj.core)
109+
}
106110
targets.configureEach {
107111
testTask.configure {
108-
enabled = project.hasProperty("runBuildSrcTests")
112+
enabled = providers.systemProperty("runBuildSrcTests").isPresent or providers.systemProperty("idea.active").isPresent
109113
}
110114
}
111115
}

buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleMavenRepoUtils.kt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.eclipse.aether.resolution.VersionRangeRequest
1212
import org.eclipse.aether.resolution.VersionRangeResult
1313
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory
1414
import org.eclipse.aether.spi.connector.transport.TransporterFactory
15+
import org.eclipse.aether.transport.file.FileTransporterFactory
1516
import org.eclipse.aether.transport.http.HttpTransporterFactory
1617
import org.eclipse.aether.version.Version
1718
import org.gradle.api.GradleException
@@ -34,13 +35,15 @@ internal object MuzzleMavenRepoUtils {
3435
}
3536

3637
/**
37-
* Create new RepositorySystem for muzzle's Maven/Aether resoltions.
38+
* Create new RepositorySystem for muzzle's Maven/Aether resolutions.
39+
* Supports both HTTP/HTTPS and file:// repositories.
3840
*/
3941
@JvmStatic
4042
fun newRepositorySystem(): RepositorySystem {
4143
val locator = MavenRepositorySystemUtils.newServiceLocator().apply {
4244
addService(RepositoryConnectorFactory::class.java, BasicRepositoryConnectorFactory::class.java)
4345
addService(TransporterFactory::class.java, HttpTransporterFactory::class.java)
46+
addService(TransporterFactory::class.java, FileTransporterFactory::class.java)
4447
}
4548
return locator.getService(RepositorySystem::class.java)
4649
}
@@ -66,15 +69,16 @@ internal object MuzzleMavenRepoUtils {
6669
fun inverseOf(
6770
muzzleDirective: MuzzleDirective,
6871
system: RepositorySystem,
69-
session: RepositorySystemSession
72+
session: RepositorySystemSession,
73+
defaultRepos: List<RemoteRepository> = MUZZLE_REPOS
7074
): Set<MuzzleDirective> {
7175
val allVersionsArtifact = DefaultArtifact(
7276
muzzleDirective.group,
7377
muzzleDirective.module,
7478
"jar",
7579
"[,)"
7680
)
77-
val repos = muzzleDirective.getRepositories(MUZZLE_REPOS)
81+
val repos = muzzleDirective.getRepositories(defaultRepos)
7882
val allRangeRequest = VersionRangeRequest().apply {
7983
repositories = repos
8084
artifact = allVersionsArtifact
@@ -119,7 +123,8 @@ internal object MuzzleMavenRepoUtils {
119123
fun resolveVersionRange(
120124
muzzleDirective: MuzzleDirective,
121125
system: RepositorySystem,
122-
session: RepositorySystemSession
126+
session: RepositorySystemSession,
127+
defaultRepos: List<RemoteRepository> = MUZZLE_REPOS
123128
): VersionRangeResult {
124129
val directiveArtifact: Artifact = DefaultArtifact(
125130
muzzleDirective.group,
@@ -129,7 +134,7 @@ internal object MuzzleMavenRepoUtils {
129134
muzzleDirective.versions
130135
)
131136
val rangeRequest = VersionRangeRequest().apply {
132-
repositories = muzzleDirective.getRepositories(MUZZLE_REPOS)
137+
repositories = muzzleDirective.getRepositories(defaultRepos)
133138
artifact = directiveArtifact
134139
}
135140

buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package datadog.gradle.plugin.muzzle
22

3-
import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils.inverseOf
4-
import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils.muzzleDirectiveToArtifacts
5-
import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils.resolveVersionRange
63
import datadog.gradle.plugin.muzzle.tasks.MuzzleEndTask
74
import datadog.gradle.plugin.muzzle.tasks.MuzzleGenerateReportTask
85
import datadog.gradle.plugin.muzzle.tasks.MuzzleGetReferencesTask
96
import datadog.gradle.plugin.muzzle.tasks.MuzzleMergeReportsTask
107
import datadog.gradle.plugin.muzzle.tasks.MuzzleTask
8+
import datadog.gradle.plugin.muzzle.planner.MuzzleTaskPlanner
119
import org.eclipse.aether.artifact.Artifact
1210
import org.gradle.api.NamedDomainObjectProvider
1311
import org.gradle.api.Plugin
@@ -101,14 +99,15 @@ class MuzzlePlugin : Plugin<Project> {
10199
project.tasks.register<MuzzleMergeReportsTask>("mergeMuzzleReports")
102100

103101
val hasRelevantTask = project.gradle.startParameter.taskNames.any { taskName ->
104-
// removing leading ':' if present
105-
val muzzleTaskName = taskName.removePrefix(":")
106-
val projectPath = project.path.removePrefix(":")
107-
muzzleTaskName == "muzzle" || "$projectPath:muzzle" == muzzleTaskName ||
108-
muzzleTaskName == "runMuzzle"
102+
val taskProjectPath = taskName.substringBeforeLast(":", "")
103+
val taskNameOnly = taskName.substringAfterLast(":")
104+
val isRelevantForProject = taskProjectPath.isEmpty() || taskProjectPath == project.path
105+
106+
isRelevantForProject && taskNameOnly.endsWith("muzzle", ignoreCase = true)
109107
}
110108
if (!hasRelevantTask) {
111109
// Adding muzzle dependencies has a large config overhead. Stop unless muzzle is explicitly run.
110+
project.logger.info("No muzzle tasks invoked for ${project.path}, skipping muzzle task planification")
112111
return
113112
}
114113

@@ -117,40 +116,19 @@ class MuzzlePlugin : Plugin<Project> {
117116

118117
val system = MuzzleMavenRepoUtils.newRepositorySystem()
119118
val session = MuzzleMavenRepoUtils.newRepositorySystemSession(system)
119+
val taskPlanner = MuzzleTaskPlanner.from(system, session)
120120
project.afterEvaluate {
121121
// use runAfter to set up task finalizers in version order
122122
var runAfter: TaskProvider<MuzzleTask> = muzzleTask
123123
val muzzleReportTasks = mutableListOf<TaskProvider<MuzzleTask>>()
124-
125-
project.extensions.getByType<MuzzleExtension>().directives.forEach { directive ->
126-
project.logger.debug("configuring {}", directive)
127-
128-
if (directive.isCoreJdk) {
129-
runAfter = addMuzzleTask(directive, null, project, runAfter, muzzleBootstrap, muzzleTooling)
130-
muzzleReportTasks.add(runAfter)
131-
} else {
132-
val range = resolveVersionRange(directive, system, session)
133-
134-
muzzleDirectiveToArtifacts(directive, range).forEach {
135-
runAfter = addMuzzleTask(directive, it, project, runAfter, muzzleBootstrap, muzzleTooling)
136-
muzzleReportTasks.add(runAfter)
137-
}
138-
139-
if (directive.assertInverse) {
140-
inverseOf(directive, system, session).forEach { inverseDirective ->
141-
val inverseRange = resolveVersionRange(inverseDirective, system, session)
142-
143-
muzzleDirectiveToArtifacts(inverseDirective, inverseRange).forEach {
144-
runAfter = addMuzzleTask(inverseDirective, it, project, runAfter, muzzleBootstrap, muzzleTooling)
145-
muzzleReportTasks.add(runAfter)
146-
}
147-
}
148-
}
149-
}
150-
project.logger.info("configured $directive")
124+
val directives = project.extensions.getByType<MuzzleExtension>().directives
125+
taskPlanner.plan(directives).forEach { plan ->
126+
runAfter = registerMuzzleTask(plan.directive, plan.artifact, project, runAfter, muzzleBootstrap, muzzleTooling)
127+
muzzleReportTasks.add(runAfter)
128+
project.logger.info("configured ${plan.directive}")
151129
}
152130

153-
if (muzzleReportTasks.isEmpty() && !project.extensions.getByType<MuzzleExtension>().directives.any { it.assertPass }) {
131+
if (muzzleReportTasks.isEmpty() && !directives.any { it.assertPass }) {
154132
muzzleReportTasks.add(muzzleTask)
155133
}
156134

@@ -180,7 +158,7 @@ class MuzzlePlugin : Plugin<Project> {
180158
* @param muzzleTooling The configuration provider for agent tooling dependencies.
181159
* @return The muzzle task provider.
182160
*/
183-
private fun addMuzzleTask(
161+
private fun registerMuzzleTask(
184162
muzzleDirective: MuzzleDirective,
185163
versionArtifact: Artifact?,
186164
instrumentationProject: Project,

buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleVersionUtils.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ internal object MuzzleVersionUtils {
6868
/**
6969
* Select a random set of versions to test
7070
*/
71-
private val RANGE_COUNT_LIMIT = 25
71+
internal val RANGE_COUNT_LIMIT = 25
7272

7373
/**
7474
* Select a random set of versions to test, limiting the range for efficiency.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package datadog.gradle.plugin.muzzle.planner
2+
3+
import datadog.gradle.plugin.muzzle.MuzzleDirective
4+
import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils
5+
import org.eclipse.aether.RepositorySystem
6+
import org.eclipse.aether.RepositorySystemSession
7+
import org.eclipse.aether.artifact.Artifact
8+
9+
/**
10+
* Default [MuzzleResolutionService] implementation backed by Maven/Aether resolution.
11+
*/
12+
internal class MavenMuzzleResolutionService(
13+
private val system: RepositorySystem,
14+
private val session: RepositorySystemSession,
15+
) : MuzzleResolutionService {
16+
override fun resolveArtifacts(directive: MuzzleDirective): Set<Artifact> {
17+
val range = MuzzleMavenRepoUtils.resolveVersionRange(directive, system, session)
18+
return MuzzleMavenRepoUtils.muzzleDirectiveToArtifacts(directive, range)
19+
}
20+
21+
override fun inverseOf(directive: MuzzleDirective): Set<MuzzleDirective> =
22+
MuzzleMavenRepoUtils.inverseOf(directive, system, session)
23+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package datadog.gradle.plugin.muzzle.planner
2+
3+
import datadog.gradle.plugin.muzzle.MuzzleDirective
4+
import org.eclipse.aether.artifact.Artifact
5+
6+
/**
7+
* Resolves muzzle directives into concrete artifacts and inverse directives.
8+
*/
9+
internal interface MuzzleResolutionService {
10+
/**
11+
* Resolves all dependency artifacts to test for the given directive.
12+
*/
13+
fun resolveArtifacts(directive: MuzzleDirective): Set<Artifact>
14+
15+
/**
16+
* Computes directives representing the inverse of the given directive.
17+
*/
18+
fun inverseOf(directive: MuzzleDirective): Set<MuzzleDirective>
19+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package datadog.gradle.plugin.muzzle.planner
2+
3+
import datadog.gradle.plugin.muzzle.MuzzleDirective
4+
import org.eclipse.aether.artifact.Artifact
5+
6+
/**
7+
* Planned unit of muzzle work for task creation.
8+
*
9+
* For `coreJdk()` directives, [artifact] is `null`.
10+
*/
11+
internal data class MuzzleTaskPlan(
12+
val directive: MuzzleDirective,
13+
val artifact: Artifact?,
14+
)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package datadog.gradle.plugin.muzzle.planner
2+
3+
import datadog.gradle.plugin.muzzle.MuzzleDirective
4+
import org.eclipse.aether.RepositorySystem
5+
import org.eclipse.aether.RepositorySystemSession
6+
7+
/**
8+
* Expands configured directives into ordered task plans.
9+
*/
10+
internal class MuzzleTaskPlanner(
11+
private val resolutionService: MuzzleResolutionService,
12+
) {
13+
companion object {
14+
fun from(system: RepositorySystem, session: RepositorySystemSession): MuzzleTaskPlanner =
15+
MuzzleTaskPlanner(MavenMuzzleResolutionService(system, session))
16+
}
17+
18+
/**
19+
* Expands declared muzzle directives into executable task plans.
20+
*
21+
* Planning rules:
22+
* - Core-JDK directives (`coreJdk()`) create exactly one [MuzzleTaskPlan] with `artifact = null`.
23+
* - Non-core directives are resolved with [MuzzleResolutionService.resolveArtifacts], creating one
24+
* plan per resolved artifact.
25+
* - If a non-core directive has `assertInverse = true`, inverse directives are obtained from
26+
* [MuzzleResolutionService.inverseOf], then each inverse directive is resolved and expanded with
27+
* the same one-plan-per-artifact rule.
28+
*
29+
* Ordering:
30+
* - The input [directives] order is preserved.
31+
* - Direct plans for a directive are emitted before its inverse plans.
32+
* - Artifact plan order follows the iteration order returned by the resolution service.
33+
*
34+
* No de-duplication is performed here. If needed, de-duplication must be handled by callers or by
35+
* the resolution service implementation.
36+
*/
37+
fun plan(directives: List<MuzzleDirective>): List<MuzzleTaskPlan> = buildList {
38+
directives.forEach { directive ->
39+
if (directive.isCoreJdk) {
40+
add(MuzzleTaskPlan(directive, null))
41+
} else {
42+
resolutionService.resolveArtifacts(directive).forEach { artifact ->
43+
add(MuzzleTaskPlan(directive, artifact))
44+
}
45+
if (directive.assertInverse) {
46+
resolutionService.inverseOf(directive).forEach { inverseDirective ->
47+
resolutionService.resolveArtifacts(inverseDirective).forEach { artifact ->
48+
add(MuzzleTaskPlan(inverseDirective, artifact))
49+
}
50+
}
51+
}
52+
}
53+
}
54+
}
55+
}

buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleTask.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,17 @@ abstract class MuzzleTask @Inject constructor(
7474
@TaskAction
7575
fun muzzle() {
7676
when {
77+
// Version-specific task: created by MuzzlePlugin for each resolved artifact.
78+
muzzleDirective.isPresent -> {
79+
assertMuzzle(muzzleDirective.get())
80+
}
81+
// Fallback for the root "muzzle" lifecycle task when no pass{} directives are
82+
// declared. In that case there are no version-specific pass tasks, so we assert
83+
// the instrumentation against its own compile-time classpath as a basic sanity check.
7784
!project.extensions.getByType<MuzzleExtension>().directives.any { it.assertPass } -> {
7885
project.logger.info("No muzzle pass directives configured. Asserting pass against instrumentation compile-time dependencies")
7986
assertMuzzle()
8087
}
81-
muzzleDirective.isPresent -> {
82-
assertMuzzle(muzzleDirective.get())
83-
}
8488
}
8589
}
8690

0 commit comments

Comments
 (0)