524 lines
22 KiB
Groovy
524 lines
22 KiB
Groovy
/*
|
|
* Copyright The OpenTelemetry Authors
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import java.lang.reflect.Method
|
|
import java.security.SecureClassLoader
|
|
import java.util.concurrent.atomic.AtomicReference
|
|
import java.util.function.Predicate
|
|
import java.util.regex.Pattern
|
|
import org.apache.maven.repository.internal.MavenRepositorySystemUtils
|
|
import org.eclipse.aether.DefaultRepositorySystemSession
|
|
import org.eclipse.aether.RepositorySystem
|
|
import org.eclipse.aether.RepositorySystemSession
|
|
import org.eclipse.aether.artifact.Artifact
|
|
import org.eclipse.aether.artifact.DefaultArtifact
|
|
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory
|
|
import org.eclipse.aether.impl.DefaultServiceLocator
|
|
import org.eclipse.aether.repository.LocalRepository
|
|
import org.eclipse.aether.repository.RemoteRepository
|
|
import org.eclipse.aether.resolution.VersionRangeRequest
|
|
import org.eclipse.aether.resolution.VersionRangeResult
|
|
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory
|
|
import org.eclipse.aether.spi.connector.transport.TransporterFactory
|
|
import org.eclipse.aether.transport.http.HttpTransporterFactory
|
|
import org.eclipse.aether.version.Version
|
|
import org.gradle.api.Action
|
|
import org.gradle.api.GradleException
|
|
import org.gradle.api.Plugin
|
|
import org.gradle.api.Project
|
|
import org.gradle.api.Task
|
|
import org.gradle.api.model.ObjectFactory
|
|
|
|
/**
|
|
* muzzle task plugin which runs muzzle validation against a range of dependencies.
|
|
*/
|
|
class MuzzlePlugin implements Plugin<Project> {
|
|
/**
|
|
* Select a random set of versions to test
|
|
*/
|
|
private static final int RANGE_COUNT_LIMIT = 10
|
|
/**
|
|
* Remote repositories used to query version ranges and fetch dependencies
|
|
*/
|
|
private static final List<RemoteRepository> MUZZLE_REPOS
|
|
private static final AtomicReference<ClassLoader> TOOLING_LOADER = new AtomicReference<>()
|
|
|
|
static {
|
|
RemoteRepository central = new RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2/").build()
|
|
RemoteRepository sonatype = new RemoteRepository.Builder("sonatype", "default", "https://oss.sonatype.org/content/repositories/releases/").build()
|
|
RemoteRepository jcenter = new RemoteRepository.Builder("jcenter", "default", "https://jcenter.bintray.com/").build()
|
|
RemoteRepository spring = new RemoteRepository.Builder("spring", "default", "https://repo.spring.io/libs-release/").build()
|
|
RemoteRepository jboss = new RemoteRepository.Builder("jboss", "default", "https://repository.jboss.org/nexus/content/repositories/releases/").build()
|
|
RemoteRepository typesafe = new RemoteRepository.Builder("typesafe", "default", "https://repo.typesafe.com/typesafe/releases").build()
|
|
RemoteRepository akka = new RemoteRepository.Builder("akka", "default", "https://dl.bintray.com/akka/maven/").build()
|
|
RemoteRepository atlassian = new RemoteRepository.Builder("atlassian", "default", "https://maven.atlassian.com/content/repositories/atlassian-public/").build()
|
|
// MUZZLE_REPOS = Arrays.asList(central, sonatype, jcenter, spring, jboss, typesafe, akka, atlassian)
|
|
MUZZLE_REPOS = Arrays.asList(central, jcenter, typesafe)
|
|
}
|
|
|
|
@Override
|
|
void apply(Project project) {
|
|
def bootstrapProject = project.rootProject.getChildProjects().get('javaagent-bootstrap')
|
|
def toolingProject = project.rootProject.getChildProjects().get('javaagent-tooling')
|
|
project.extensions.create("muzzle", MuzzleExtension, project.objects)
|
|
|
|
// compileMuzzle compiles all projects required to run muzzle validation.
|
|
// Not adding group and description to keep this task from showing in `gradle tasks`.
|
|
def compileMuzzle = project.task('compileMuzzle')
|
|
def muzzle = project.task('muzzle') {
|
|
group = 'Muzzle'
|
|
description = "Run instrumentation muzzle on compile time dependencies"
|
|
doLast {
|
|
if (!project.muzzle.directives.any { it.assertPass }) {
|
|
project.getLogger().info('No muzzle pass directives configured. Asserting pass against instrumentation compile-time dependencies')
|
|
ClassLoader userCL = createCompileDepsClassLoader(project, bootstrapProject)
|
|
ClassLoader instrumentationCL = createInstrumentationClassloader(project, toolingProject)
|
|
Method assertionMethod = instrumentationCL.loadClass('io.opentelemetry.javaagent.tooling.muzzle.matcher.MuzzleGradlePluginUtil')
|
|
.getMethod('assertInstrumentationMuzzled', ClassLoader.class, ClassLoader.class, boolean.class)
|
|
assertionMethod.invoke(null, instrumentationCL, userCL, true)
|
|
}
|
|
println "Muzzle executing for $project"
|
|
}
|
|
}
|
|
def printReferences = project.task('printReferences') {
|
|
group = 'Muzzle'
|
|
description = "Print references created by instrumentation muzzle"
|
|
doLast {
|
|
ClassLoader instrumentationCL = createInstrumentationClassloader(project, toolingProject)
|
|
Method assertionMethod = instrumentationCL.loadClass('io.opentelemetry.javaagent.tooling.muzzle.matcher.MuzzleGradlePluginUtil')
|
|
.getMethod('printMuzzleReferences', ClassLoader.class)
|
|
assertionMethod.invoke(null, instrumentationCL)
|
|
}
|
|
}
|
|
project.tasks.compileMuzzle.dependsOn(bootstrapProject.tasks.compileJava)
|
|
project.tasks.compileMuzzle.dependsOn(toolingProject.tasks.compileJava)
|
|
project.afterEvaluate {
|
|
project.tasks.compileMuzzle.dependsOn(project.tasks.compileJava)
|
|
if (project.tasks.getNames().contains('compileScala')) {
|
|
project.tasks.compileMuzzle.dependsOn(project.tasks.compileScala)
|
|
}
|
|
}
|
|
project.tasks.muzzle.dependsOn(project.tasks.compileMuzzle)
|
|
project.tasks.printReferences.dependsOn(project.tasks.compileMuzzle)
|
|
|
|
def hasRelevantTask = project.gradle.startParameter.taskNames.any { taskName ->
|
|
// removing leading ':' if present
|
|
taskName = taskName.replaceFirst('^:', '')
|
|
String muzzleTaskPath = project.path.replaceFirst('^:', '')
|
|
return 'muzzle' == taskName || "${muzzleTaskPath}:muzzle" == taskName
|
|
}
|
|
if (!hasRelevantTask) {
|
|
// Adding muzzle dependencies has a large config overhead. Stop unless muzzle is explicitly run.
|
|
return
|
|
}
|
|
|
|
RepositorySystem system = newRepositorySystem()
|
|
RepositorySystemSession session = newRepositorySystemSession(system)
|
|
|
|
project.afterEvaluate {
|
|
// use runAfter to set up task finalizers in version order
|
|
Task runAfter = project.tasks.muzzle
|
|
|
|
for (MuzzleDirective muzzleDirective : project.muzzle.directives) {
|
|
project.getLogger().info("configured $muzzleDirective")
|
|
|
|
if (muzzleDirective.coreJdk) {
|
|
runAfter = addMuzzleTask(muzzleDirective, null, project, runAfter, bootstrapProject, toolingProject)
|
|
} else {
|
|
muzzleDirectiveToArtifacts(muzzleDirective, system, session).collect() { Artifact singleVersion ->
|
|
runAfter = addMuzzleTask(muzzleDirective, singleVersion, project, runAfter, bootstrapProject, toolingProject)
|
|
}
|
|
if (muzzleDirective.assertInverse) {
|
|
inverseOf(muzzleDirective, system, session).collect() { MuzzleDirective inverseDirective ->
|
|
muzzleDirectiveToArtifacts(inverseDirective, system, session).collect() { Artifact singleVersion ->
|
|
runAfter = addMuzzleTask(inverseDirective, singleVersion, project, runAfter, bootstrapProject, toolingProject)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static ClassLoader getOrCreateToolingLoader(Project toolingProject) {
|
|
synchronized (TOOLING_LOADER) {
|
|
ClassLoader toolingLoader = TOOLING_LOADER.get()
|
|
if (toolingLoader == null) {
|
|
Set<URL> urls = new HashSet<>()
|
|
toolingProject.getLogger().info('creating classpath for auto-tooling')
|
|
for (File f : toolingProject.sourceSets.main.runtimeClasspath.getFiles()) {
|
|
toolingProject.getLogger().info('--' + f)
|
|
urls.add(f.toURI().toURL())
|
|
}
|
|
def loader = new URLClassLoader(urls.toArray(new URL[0]), ClassLoader.platformClassLoader)
|
|
assert TOOLING_LOADER.compareAndSet(null, loader)
|
|
return TOOLING_LOADER.get()
|
|
} else {
|
|
return toolingLoader
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a classloader with core agent classes and project instrumentation on the classpath.
|
|
*/
|
|
private static ClassLoader createInstrumentationClassloader(Project project, Project toolingProject) {
|
|
project.getLogger().info("Creating instrumentation classpath for: " + project.getName())
|
|
Set<URL> urls = new HashSet<>()
|
|
for (File f : project.sourceSets.main.runtimeClasspath.getFiles()) {
|
|
project.getLogger().info('--' + f)
|
|
urls.add(f.toURI().toURL())
|
|
}
|
|
|
|
return new URLClassLoader(urls.toArray(new URL[0]), getOrCreateToolingLoader(toolingProject))
|
|
}
|
|
|
|
/**
|
|
* Create a classloader with all compile-time dependencies on the classpath
|
|
*/
|
|
private static ClassLoader createCompileDepsClassLoader(Project project, Project bootstrapProject) {
|
|
List<URL> userUrls = new ArrayList<>()
|
|
project.getLogger().info("Creating compile-time classpath for: " + project.getName())
|
|
for (File f : project.configurations.compileOnly.getFiles()) {
|
|
project.getLogger().info('--' + f)
|
|
userUrls.add(f.toURI().toURL())
|
|
}
|
|
for (File f : bootstrapProject.sourceSets.main.runtimeClasspath.getFiles()) {
|
|
project.getLogger().info('--' + f)
|
|
userUrls.add(f.toURI().toURL())
|
|
}
|
|
return new URLClassLoader(userUrls.toArray(new URL[0]), ClassLoader.platformClassLoader)
|
|
}
|
|
|
|
/**
|
|
* Create a classloader with dependencies for a single muzzle task.
|
|
*/
|
|
private static ClassLoader createClassLoaderForTask(Project project, Project bootstrapProject, String muzzleTaskName) {
|
|
List<URL> userUrls = new ArrayList<>()
|
|
|
|
project.getLogger().info("Creating task classpath")
|
|
project.configurations.getByName(muzzleTaskName).resolvedConfiguration.files.each { File jarFile ->
|
|
project.getLogger().info("-- Added to instrumentation classpath: $jarFile")
|
|
userUrls.add(jarFile.toURI().toURL())
|
|
}
|
|
|
|
for (File f : bootstrapProject.sourceSets.main.runtimeClasspath.getFiles()) {
|
|
project.getLogger().info("-- Added to instrumentation bootstrap classpath: $f")
|
|
userUrls.add(f.toURI().toURL())
|
|
}
|
|
return new URLClassLoader(userUrls.toArray(new URL[0]), ClassLoader.platformClassLoader)
|
|
}
|
|
|
|
/**
|
|
* Convert a muzzle directive to a list of artifacts
|
|
*/
|
|
private static Set<Artifact> muzzleDirectiveToArtifacts(MuzzleDirective muzzleDirective, RepositorySystem system, RepositorySystemSession session) {
|
|
Artifact directiveArtifact = new DefaultArtifact(muzzleDirective.group, muzzleDirective.module, "jar", muzzleDirective.versions)
|
|
|
|
VersionRangeRequest rangeRequest = new VersionRangeRequest()
|
|
rangeRequest.setRepositories(MUZZLE_REPOS)
|
|
rangeRequest.setArtifact(directiveArtifact)
|
|
VersionRangeResult rangeResult = system.resolveVersionRange(session, rangeRequest)
|
|
|
|
Set<Artifact> allVersionArtifacts = filterVersions(rangeResult, muzzleDirective.skipVersions).collect { version ->
|
|
new DefaultArtifact(muzzleDirective.group, muzzleDirective.module, "jar", version)
|
|
}.toSet()
|
|
|
|
if (allVersionArtifacts.isEmpty()) {
|
|
throw new GradleException("No muzzle artifacts found for $muzzleDirective.group:$muzzleDirective.module $muzzleDirective.versions")
|
|
}
|
|
|
|
return allVersionArtifacts
|
|
}
|
|
|
|
/**
|
|
* Create a list of muzzle directives which assert the opposite of the given MuzzleDirective.
|
|
*/
|
|
private static Set<MuzzleDirective> inverseOf(MuzzleDirective muzzleDirective, RepositorySystem system, RepositorySystemSession session) {
|
|
Set<MuzzleDirective> inverseDirectives = new HashSet<>()
|
|
|
|
Artifact allVersionsArtifact = new DefaultArtifact(muzzleDirective.group, muzzleDirective.module, "jar", "[,)")
|
|
Artifact directiveArtifact = new DefaultArtifact(muzzleDirective.group, muzzleDirective.module, "jar", muzzleDirective.versions)
|
|
|
|
VersionRangeRequest allRangeRequest = new VersionRangeRequest()
|
|
allRangeRequest.setRepositories(MUZZLE_REPOS)
|
|
allRangeRequest.setArtifact(allVersionsArtifact)
|
|
VersionRangeResult allRangeResult = system.resolveVersionRange(session, allRangeRequest)
|
|
|
|
VersionRangeRequest rangeRequest = new VersionRangeRequest()
|
|
rangeRequest.setRepositories(MUZZLE_REPOS)
|
|
rangeRequest.setArtifact(directiveArtifact)
|
|
VersionRangeResult rangeResult = system.resolveVersionRange(session, rangeRequest)
|
|
|
|
allRangeResult.getVersions().removeAll(rangeResult.getVersions())
|
|
|
|
filterVersions(allRangeResult, muzzleDirective.skipVersions).each { version ->
|
|
MuzzleDirective inverseDirective = new MuzzleDirective()
|
|
inverseDirective.group = muzzleDirective.group
|
|
inverseDirective.module = muzzleDirective.module
|
|
inverseDirective.versions = version
|
|
inverseDirective.assertPass = !muzzleDirective.assertPass
|
|
inverseDirectives.add(inverseDirective)
|
|
}
|
|
|
|
return inverseDirectives
|
|
}
|
|
|
|
private static Set<String> filterVersions(VersionRangeResult range, Set<String> skipVersions) {
|
|
Set<String> result = new HashSet<>()
|
|
|
|
def predicate = new AcceptableVersions(range, skipVersions)
|
|
if (predicate.test(range.lowestVersion)) {
|
|
result.add(range.lowestVersion.toString())
|
|
}
|
|
if (predicate.test(range.highestVersion)) {
|
|
result.add(range.highestVersion.toString())
|
|
}
|
|
|
|
List<Version> copy = new ArrayList<>(range.versions)
|
|
Collections.shuffle(copy)
|
|
while (result.size() < RANGE_COUNT_LIMIT && !copy.isEmpty()) {
|
|
Version version = copy.pop()
|
|
if (predicate.test(version)) {
|
|
result.add(version.toString())
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
static class AcceptableVersions implements Predicate<Version> {
|
|
private static final Pattern GIT_SHA_PATTERN = Pattern.compile('^.*-[0-9a-f]{7,}$')
|
|
|
|
private final VersionRangeResult range
|
|
private final Collection<String> skipVersions
|
|
|
|
AcceptableVersions(VersionRangeResult range, Collection<String> skipVersions) {
|
|
this.range = range
|
|
this.skipVersions = skipVersions
|
|
}
|
|
|
|
@Override
|
|
boolean test(Version version) {
|
|
if (version == null) {
|
|
return false
|
|
}
|
|
def versionString = version.toString().toLowerCase()
|
|
if (skipVersions.contains(versionString)) {
|
|
return false
|
|
}
|
|
|
|
def draftVersion = versionString.contains("rc") ||
|
|
versionString.contains(".cr") ||
|
|
versionString.contains("alpha") ||
|
|
versionString.contains("beta") ||
|
|
versionString.contains("-b") ||
|
|
versionString.contains(".m") ||
|
|
versionString.contains("-m") ||
|
|
versionString.contains("-dev") ||
|
|
versionString.contains("-ea") ||
|
|
versionString.contains("-atlassian-") ||
|
|
versionString.contains("public_draft") ||
|
|
versionString.matches(GIT_SHA_PATTERN)
|
|
|
|
return !draftVersion
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Configure a muzzle task to pass or fail a given version.
|
|
*
|
|
* @param assertPass If true, assert that muzzle validation passes
|
|
* @param versionArtifact version to assert against.
|
|
* @param instrumentationProject instrumentation being asserted against.
|
|
* @param runAfter Task which runs before the new muzzle task.
|
|
* @param bootstrapProject Agent bootstrap project.
|
|
* @param toolingProject Agent tooling project.
|
|
*
|
|
* @return The created muzzle task.
|
|
*/
|
|
private static Task addMuzzleTask(MuzzleDirective muzzleDirective, Artifact versionArtifact, Project instrumentationProject, Task runAfter, Project bootstrapProject, Project toolingProject) {
|
|
def taskName
|
|
if (muzzleDirective.coreJdk) {
|
|
taskName = "muzzle-Assert$muzzleDirective"
|
|
} else {
|
|
taskName = "muzzle-Assert${muzzleDirective.assertPass ? "Pass" : "Fail"}-$versionArtifact.groupId-$versionArtifact.artifactId-$versionArtifact.version${muzzleDirective.name ? "-${muzzleDirective.getNameSlug()}" : ""}"
|
|
}
|
|
def config = instrumentationProject.configurations.create(taskName)
|
|
|
|
if (!muzzleDirective.coreJdk) {
|
|
def dep = instrumentationProject.dependencies.create("$versionArtifact.groupId:$versionArtifact.artifactId:$versionArtifact.version") {
|
|
transitive = true
|
|
}
|
|
// The following optional transitive dependencies are brought in by some legacy module such as log4j 1.x but are no
|
|
// longer bundled with the JVM and have to be excluded for the muzzle tests to be able to run.
|
|
dep.exclude group: 'com.sun.jdmk', module: 'jmxtools'
|
|
dep.exclude group: 'com.sun.jmx', module: 'jmxri'
|
|
|
|
config.dependencies.add(dep)
|
|
}
|
|
for (String additionalDependency : muzzleDirective.additionalDependencies) {
|
|
config.dependencies.add(instrumentationProject.dependencies.create(additionalDependency) {
|
|
transitive = true
|
|
})
|
|
}
|
|
|
|
def muzzleTask = instrumentationProject.task(taskName) {
|
|
dependsOn(instrumentationProject.configurations.named("runtimeClasspath"))
|
|
doLast {
|
|
ClassLoader instrumentationCL = createInstrumentationClassloader(instrumentationProject, toolingProject)
|
|
def ccl = Thread.currentThread().contextClassLoader
|
|
def bogusLoader = new SecureClassLoader() {
|
|
@Override
|
|
String toString() {
|
|
return "bogus"
|
|
}
|
|
|
|
}
|
|
Thread.currentThread().contextClassLoader = bogusLoader
|
|
ClassLoader userCL = createClassLoaderForTask(instrumentationProject, bootstrapProject, taskName)
|
|
try {
|
|
// find all instrumenters, get muzzle, and assert
|
|
Method assertionMethod = instrumentationCL.loadClass('io.opentelemetry.javaagent.tooling.muzzle.matcher.MuzzleGradlePluginUtil')
|
|
.getMethod('assertInstrumentationMuzzled', ClassLoader.class, ClassLoader.class, boolean.class)
|
|
assertionMethod.invoke(null, instrumentationCL, userCL, muzzleDirective.assertPass)
|
|
} finally {
|
|
Thread.currentThread().contextClassLoader = ccl
|
|
}
|
|
|
|
for (Thread thread : Thread.getThreads()) {
|
|
if (thread.contextClassLoader == bogusLoader || thread.contextClassLoader == instrumentationCL || thread.contextClassLoader == userCL) {
|
|
throw new GradleException("Task $taskName has spawned a thread: $thread with classloader $thread.contextClassLoader. This will prevent GC of dynamic muzzle classes. Aborting muzzle run.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
runAfter.finalizedBy(muzzleTask)
|
|
return muzzleTask
|
|
}
|
|
|
|
/**
|
|
* Create muzzle's repository system
|
|
*/
|
|
private static RepositorySystem newRepositorySystem() {
|
|
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator()
|
|
locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class)
|
|
locator.addService(TransporterFactory.class, HttpTransporterFactory.class)
|
|
|
|
return locator.getService(RepositorySystem.class)
|
|
}
|
|
|
|
|
|
/**
|
|
* Create muzzle's repository system session
|
|
*/
|
|
private static RepositorySystemSession newRepositorySystemSession(RepositorySystem system) {
|
|
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession()
|
|
|
|
def tempDir = File.createTempDir()
|
|
tempDir.deleteOnExit()
|
|
LocalRepository localRepo = new LocalRepository(tempDir)
|
|
session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo))
|
|
|
|
return session
|
|
}
|
|
}
|
|
|
|
// plugin extension classes
|
|
|
|
/**
|
|
* A pass or fail directive for a single dependency.
|
|
*/
|
|
class MuzzleDirective {
|
|
|
|
/**
|
|
* Name is optional and is used to further define the scope of a directive. The motivation for this is that this
|
|
* plugin creates a config for each of the dependencies under test with name '...-<group_id>-<artifact_id>-<version>'.
|
|
* The problem is that if we want to test multiple times the same configuration under different conditions, e.g.
|
|
* with different extra dependencies, the plugin would throw an error as it would try to create several times the
|
|
* same config. This property can be used to differentiate those config names for different directives.
|
|
*/
|
|
String name
|
|
|
|
String group
|
|
String module
|
|
String versions
|
|
Set<String> skipVersions = new HashSet<>()
|
|
List<String> additionalDependencies = new ArrayList<>()
|
|
boolean assertPass
|
|
boolean assertInverse = false
|
|
boolean coreJdk = false
|
|
|
|
void coreJdk() {
|
|
coreJdk = true
|
|
}
|
|
|
|
/**
|
|
* Adds extra dependencies to the current muzzle test.
|
|
*
|
|
* @param compileString An extra dependency in the gradle canonical form: '<group_id>:<artifact_id>:<version_id>'.
|
|
*/
|
|
void extraDependency(String compileString) {
|
|
additionalDependencies.add(compileString)
|
|
}
|
|
|
|
/**
|
|
* Slug of directive name.
|
|
*
|
|
* @return A slug of the name or an empty string if name is empty. E.g. 'My Directive' --> 'My-Directive'
|
|
*/
|
|
String getNameSlug() {
|
|
if (null == name) {
|
|
return ""
|
|
}
|
|
|
|
return name.trim().replaceAll("[^a-zA-Z0-9]+", "-")
|
|
}
|
|
|
|
String toString() {
|
|
if (coreJdk) {
|
|
return "${assertPass ? 'Pass' : 'Fail'}-core-jdk"
|
|
} else {
|
|
return "${assertPass ? 'pass' : 'fail'} $group:$module:$versions"
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Muzzle extension containing all pass and fail directives.
|
|
*/
|
|
class MuzzleExtension {
|
|
final List<MuzzleDirective> directives = new ArrayList<>()
|
|
private final ObjectFactory objectFactory
|
|
|
|
@javax.inject.Inject
|
|
MuzzleExtension(final ObjectFactory objectFactory) {
|
|
this.objectFactory = objectFactory
|
|
}
|
|
|
|
void pass(Action<? super MuzzleDirective> action) {
|
|
MuzzleDirective pass = objectFactory.newInstance(MuzzleDirective)
|
|
action.execute(pass)
|
|
postConstruct(pass)
|
|
pass.assertPass = true
|
|
directives.add(pass)
|
|
}
|
|
|
|
void fail(Action<? super MuzzleDirective> action) {
|
|
MuzzleDirective fail = objectFactory.newInstance(MuzzleDirective)
|
|
action.execute(fail)
|
|
postConstruct(fail)
|
|
fail.assertPass = false
|
|
directives.add(fail)
|
|
}
|
|
|
|
private postConstruct(MuzzleDirective directive) {
|
|
// Make skipVersions case insensitive.
|
|
directive.skipVersions = directive.skipVersions.collect {
|
|
it.toLowerCase()
|
|
}
|
|
}
|
|
}
|