Add basic unit tests for SpecContext (#1377)

This commit is contained in:
modmuss
2025-10-01 11:27:14 +01:00
committed by GitHub
parent 7484a7fd95
commit 5f513b0efc
4 changed files with 384 additions and 72 deletions

View File

@@ -38,22 +38,18 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.attributes.Usage;
import org.gradle.api.plugins.JavaPlugin;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.api.processor.SpecContext;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.util.AsyncCache;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.fmj.FabricModJson;
import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
import net.fabricmc.loom.util.fmj.FabricModJsonHelpers;
import net.fabricmc.loom.util.gradle.GradleUtils;
/**
* @param modDependencies External mods that are depended on
@@ -65,20 +61,24 @@ public record SpecContextImpl(
List<FabricModJson> localMods,
List<ModHolder> compileRuntimeMods) implements SpecContext {
public static SpecContextImpl create(Project project) {
AsyncCache<List<FabricModJson>> fmjCache = new AsyncCache<List<FabricModJson>>();
return create(new SpecContextProjectView.Impl(project, LoomGradleExtension.get(project)));
}
@VisibleForTesting
public static SpecContextImpl create(SpecContextProjectView projectView) {
AsyncCache<List<FabricModJson>> fmjCache = new AsyncCache<>();
return new SpecContextImpl(
getDependentMods(project, fmjCache),
FabricModJsonHelpers.getModsInProject(project),
getCompileRuntimeMods(project, fmjCache)
getDependentMods(projectView, fmjCache),
projectView.getMods(),
getCompileRuntimeMods(projectView, fmjCache)
);
}
// Reruns a list of mods found on both the compile and/or runtime classpaths
private static List<FabricModJson> getDependentMods(Project project, AsyncCache<List<FabricModJson>> fmjCache) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
private static List<FabricModJson> getDependentMods(SpecContextProjectView projectView, AsyncCache<List<FabricModJson>> fmjCache) {
var futures = new ArrayList<CompletableFuture<List<FabricModJson>>>();
for (RemapConfigurationSettings entry : extension.getRemapConfigurations()) {
for (RemapConfigurationSettings entry : projectView.extension().getRemapConfigurations()) {
final Set<File> artifacts = entry.getSourceConfiguration().get().resolve();
for (File artifact : artifacts) {
@@ -90,10 +90,9 @@ public record SpecContextImpl(
}
}
// TODO provide a project isolated way of doing this.
if (!extension.isProjectIsolationActive() && !GradleUtils.getBooleanProperty(project, Constants.Properties.DISABLE_PROJECT_DEPENDENT_MODS)) {
if (!projectView.disableProjectDependantMods()) {
// Add all the dependent projects
for (Project dependentProject : getDependentProjects(project).toList()) {
for (Project dependentProject : getDependentProjects(projectView).toList()) {
futures.add(fmjCache.get(dependentProject.getPath(), () -> FabricModJsonHelpers.getModsInProject(dependentProject)));
}
}
@@ -101,19 +100,19 @@ public record SpecContextImpl(
return sorted(AsyncCache.joinList(futures));
}
private static Stream<Project> getDependentProjects(Project project) {
final Stream<Project> runtimeProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME));
final Stream<Project> compileProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
private static Stream<Project> getDependentProjects(SpecContextProjectView projectView) {
final Stream<Project> runtimeProjects = projectView.getLoomProjectDependencies(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
final Stream<Project> compileProjects = projectView.getLoomProjectDependencies(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME);
return Stream.concat(runtimeProjects, compileProjects)
.distinct();
}
// Returns a list of mods that are on both to compile and runtime classpath
private static List<ModHolder> getCompileRuntimeMods(Project project, AsyncCache<List<FabricModJson>> fmjCache) {
var mods = new ArrayList<>(getCompileRuntimeModsFromRemapConfigs(project, fmjCache));
private static List<ModHolder> getCompileRuntimeMods(SpecContextProjectView projectView, AsyncCache<List<FabricModJson>> fmjCache) {
var mods = new ArrayList<>(getCompileRuntimeModsFromRemapConfigs(projectView, fmjCache));
for (Project dependentProject : getCompileRuntimeProjectDependencies(project).toList()) {
for (Project dependentProject : getCompileRuntimeProjectDependencies(projectView).toList()) {
List<FabricModJson> projectMods = fmjCache.getBlocking(dependentProject.getPath(), () -> {
return FabricModJsonHelpers.getModsInProject(dependentProject);
});
@@ -127,49 +126,49 @@ public record SpecContextImpl(
}
// Returns a list of jar mods that are found on the compile and runtime remapping configurations
private static List<ModHolder> getCompileRuntimeModsFromRemapConfigs(Project project, AsyncCache<List<FabricModJson>> fmjCache) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
private static List<ModHolder> getCompileRuntimeModsFromRemapConfigs(SpecContextProjectView projectView, AsyncCache<List<FabricModJson>> fmjCache) {
// A set of mod ids from all remap configurations that are considered for dependency transforms.
final Set<String> runtimeModIds = getModIds(
project,
projectView,
fmjCache,
extension.getRuntimeRemapConfigurations().stream()
projectView.extension().getRuntimeRemapConfigurations().stream()
.filter(settings -> settings.getApplyDependencyTransforms().get())
);
// A set of mod ids that are found on one or more remap configurations that target the common source set.
// Null when split source sets are not enabled, meaning all mods are common.
final Set<String> commonModIds = extension.areEnvironmentSourceSetsSplit() ? getModIds(
project,
final Set<String> commonRuntimeModIds = projectView.extension().areEnvironmentSourceSetsSplit() ? getModIds(
projectView,
fmjCache,
extension.getRuntimeRemapConfigurations().stream()
projectView.extension().getRuntimeRemapConfigurations().stream()
.filter(settings -> settings.getSourceSet().map(sourceSet -> !sourceSet.getName().equals(MinecraftSourceSets.Split.CLIENT_ONLY_SOURCE_SET_NAME)).get())
.filter(settings -> settings.getApplyDependencyTransforms().get()))
: null;
return getMods(
project,
Stream<FabricModJson> compileMods = getMods(
projectView,
fmjCache,
extension.getCompileRemapConfigurations().stream()
.filter(settings -> settings.getApplyDependencyTransforms().get()))
projectView.extension().getCompileRemapConfigurations().stream()
.filter(settings -> settings.getApplyDependencyTransforms().get()));
return compileMods
// Only check based on the modid, as there may be differing versions used between the compile and runtime classpath.
// We assume that the version used at runtime will be binary compatible with the version used to compile against.
// It's not perfect but better than silently not supplying the mod, and this could happen with regular API that you compile against anyway.
.filter(fabricModJson -> runtimeModIds.contains(fabricModJson.getId()))
.sorted(Comparator.comparing(FabricModJson::getId))
.map(fabricModJson -> new ModHolder(fabricModJson, commonModIds == null || commonModIds.contains(fabricModJson.getId())))
.map(fabricModJson -> new ModHolder(fabricModJson, commonRuntimeModIds == null || commonRuntimeModIds.contains(fabricModJson.getId())))
.toList();
}
private static Stream<FabricModJson> getMods(Project project, AsyncCache<List<FabricModJson>> fmjCache, Stream<RemapConfigurationSettings> stream) {
return stream.flatMap(resolveArtifacts(project, true))
private static Stream<FabricModJson> getMods(SpecContextProjectView projectView, AsyncCache<List<FabricModJson>> fmjCache, Stream<RemapConfigurationSettings> stream) {
return stream.flatMap(projectView.resolveArtifacts(true))
.map(modFromZip(fmjCache))
.filter(Objects::nonNull);
}
private static Set<String> getModIds(Project project, AsyncCache<List<FabricModJson>> fmjCache, Stream<RemapConfigurationSettings> stream) {
return getMods(project, fmjCache, stream)
private static Set<String> getModIds(SpecContextProjectView projectView, AsyncCache<List<FabricModJson>> fmjCache, Stream<RemapConfigurationSettings> stream) {
return getMods(projectView, fmjCache, stream)
.map(FabricModJson::getId)
.collect(Collectors.toSet());
}
@@ -185,43 +184,19 @@ public record SpecContextImpl(
};
}
private static Function<RemapConfigurationSettings, Stream<Path>> resolveArtifacts(Project project, boolean runtime) {
final Usage usage = project.getObjects().named(Usage.class, runtime ? Usage.JAVA_RUNTIME : Usage.JAVA_API);
return settings -> {
final Configuration configuration = settings.getSourceConfiguration().get().copyRecursive();
configuration.setCanBeConsumed(false);
configuration.attributes(attributes -> attributes.attribute(Usage.USAGE_ATTRIBUTE, usage));
return configuration.resolve().stream().map(File::toPath);
};
}
// Returns a list of Loom Projects found in both the runtime and compile classpath
private static Stream<Project> getCompileRuntimeProjectDependencies(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
// TODO provide a project isolated way of doing this.
if (extension.isProjectIsolationActive()
|| GradleUtils.getBooleanProperty(project, Constants.Properties.DISABLE_PROJECT_DEPENDENT_MODS)) {
private static Stream<Project> getCompileRuntimeProjectDependencies(SpecContextProjectView projectView) {
if (projectView.disableProjectDependantMods()) {
return Stream.empty();
}
final Stream<Project> runtimeProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME));
final List<Project> compileProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)).toList();
final Stream<Project> runtimeProjects = projectView.getLoomProjectDependencies(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
final List<Project> compileProjects = projectView.getLoomProjectDependencies(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME).toList();
return runtimeProjects
.filter(compileProjects::contains); // Use the intersection of the two configurations.
}
// Returns a list of Loom Projects found in the provided Configuration
private static Stream<Project> getLoomProjectDependencies(Project project, Configuration configuration) {
return configuration.getAllDependencies()
.withType(ProjectDependency.class)
.stream()
.map((d) -> project.project(d.getPath()))
.filter(GradleUtils::isLoomProject);
}
// Sort to ensure stable caching
private static List<FabricModJson> sorted(List<FabricModJson> mods) {
return mods.stream().sorted(Comparator.comparing(FabricModJson::getId)).toList();

View File

@@ -0,0 +1,94 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.processors;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.attributes.Usage;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.fmj.FabricModJson;
import net.fabricmc.loom.util.fmj.FabricModJsonHelpers;
import net.fabricmc.loom.util.gradle.GradleUtils;
// Used to abstract out the Gradle API usage to ease unit testing.
public interface SpecContextProjectView {
LoomGradleExtension extension();
// Returns a list of Loom Projects found in the specified Configuration
Stream<Project> getLoomProjectDependencies(String name);
Function<RemapConfigurationSettings, Stream<Path>> resolveArtifacts(boolean runtime);
List<FabricModJson> getMods();
boolean disableProjectDependantMods();
record Impl(Project project, LoomGradleExtension extension) implements SpecContextProjectView {
@Override
public Stream<Project> getLoomProjectDependencies(String name) {
final Configuration configuration = project.getConfigurations().getByName(name);
return configuration.getAllDependencies()
.withType(ProjectDependency.class)
.stream()
.map((d) -> project.project(d.getPath()))
.filter(GradleUtils::isLoomProject);
}
@Override
public Function<RemapConfigurationSettings, Stream<Path>> resolveArtifacts(boolean runtime) {
final Usage usage = project.getObjects().named(Usage.class, runtime ? Usage.JAVA_RUNTIME : Usage.JAVA_API);
return settings -> {
final Configuration configuration = settings.getSourceConfiguration().get().copyRecursive();
configuration.setCanBeConsumed(false);
configuration.attributes(attributes -> attributes.attribute(Usage.USAGE_ATTRIBUTE, usage));
return configuration.resolve().stream().map(File::toPath);
};
}
@Override
public List<FabricModJson> getMods() {
return FabricModJsonHelpers.getModsInProject(project);
}
@Override
public boolean disableProjectDependantMods() {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
// TODO provide a project isolated way of doing this.
return extension.isProjectIsolationActive()
|| GradleUtils.getBooleanProperty(project, Constants.Properties.DISABLE_PROJECT_DEPENDENT_MODS);
}
}
}

View File

@@ -0,0 +1,239 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit
import java.nio.file.Path
import java.util.function.Function
import java.util.stream.Stream
import groovy.transform.CompileStatic
import org.gradle.api.NamedDomainObjectList
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import spock.lang.Specification
import spock.lang.TempDir
import net.fabricmc.loom.LoomGradleExtension
import net.fabricmc.loom.api.RemapConfigurationSettings
import net.fabricmc.loom.api.fmj.FabricModJsonV1Spec
import net.fabricmc.loom.configuration.processors.SpecContextImpl
import net.fabricmc.loom.configuration.processors.SpecContextProjectView
import net.fabricmc.loom.test.util.GradleTestUtil
import net.fabricmc.loom.util.ZipUtils
import net.fabricmc.loom.util.fmj.gen.FabricModJsonV1Generator
import static org.mockito.Mockito.mock
import static org.mockito.Mockito.when
@SuppressWarnings('ExplicitCallToModMethod')
class SpecContextTest extends Specification {
@TempDir
Path tempDir
Project project
LoomGradleExtension extension
SpecContextProjectView projectView
NamedDomainObjectList<RemapConfigurationSettings> remapConfigurations
RemapConfigurationSettings implementation
RemapConfigurationSettings runtimeOnly
RemapConfigurationSettings compileOnly
Map<RemapConfigurationSettings, List<Path>> runtimeArtifacts = [:]
Map<RemapConfigurationSettings, List<Path>> apiArtifacts = [:]
void setup() {
project = GradleTestUtil.mockProject()
extension = LoomGradleExtension.get(project)
projectView = mock(SpecContextProjectView.class)
remapConfigurations = project.getObjects().namedDomainObjectList(RemapConfigurationSettings.class)
when(projectView.extension()).thenReturn(extension)
when(extension.getRemapConfigurations()).thenReturn(remapConfigurations)
when(projectView.resolveArtifacts(true)).thenReturn(resolve(runtimeArtifacts))
when(projectView.resolveArtifacts(false)).thenReturn(resolve(apiArtifacts))
implementation = createConfigurationSettings("implementation")
runtimeOnly = createConfigurationSettings("runtimeOnly")
compileOnly = createConfigurationSettings("compileOnly")
remapConfigurations.addAll([
implementation,
runtimeOnly,
compileOnly
])
when(extension.getCompileRemapConfigurations()).thenReturn([implementation, compileOnly])
when(extension.getRuntimeRemapConfigurations()).thenReturn([implementation, runtimeOnly])
}
def "Empty"() {
setup:
dependencies(
implementation: [],
runtimeOnly: [],
compileOnly: []
)
when:
def specContext = SpecContextImpl.create(projectView)
then:
specContext.modDependencies().size() == 0
specContext.localMods().size() == 0
specContext.modDependenciesCompileRuntime().size() == 0
specContext.modDependenciesCompileRuntimeClient().size() == 0
specContext.allMods().size() == 0
}
def "implementation dependency"() {
setup:
dependencies(
implementation: [mod("test1")],
runtimeOnly: [],
compileOnly: []
)
when:
def specContext = SpecContextImpl.create(projectView)
then:
specContext.modDependencies().size() == 1
specContext.localMods().size() == 0
specContext.modDependenciesCompileRuntime().size() == 1
specContext.modDependenciesCompileRuntimeClient().size() == 0
specContext.allMods().size() == 1
}
def "runtime only dependency"() {
setup:
dependencies(
implementation: [],
runtimeOnly: [mod("test1")],
compileOnly: []
)
when:
def specContext = SpecContextImpl.create(projectView)
then:
specContext.modDependencies().size() == 1
specContext.localMods().size() == 0
specContext.modDependenciesCompileRuntime().size() == 0
specContext.modDependenciesCompileRuntimeClient().size() == 0
specContext.allMods().size() == 1
}
def "compile only dependency"() {
setup:
dependencies(
implementation: [],
runtimeOnly: [],
compileOnly: [mod("test1")]
)
when:
def specContext = SpecContextImpl.create(projectView)
then:
specContext.modDependencies().size() == 1
specContext.localMods().size() == 0
specContext.modDependenciesCompileRuntime().size() == 0
specContext.modDependenciesCompileRuntimeClient().size() == 0
specContext.allMods().size() == 1
}
// TODO I believe this test is testing broken behaviour
def "compile only runtime only dependency"() {
setup:
def test1 = mod("test1")
dependencies(
implementation: [],
runtimeOnly: [test1],
compileOnly: [test1]
)
when:
def specContext = SpecContextImpl.create(projectView)
then:
specContext.modDependencies().size() == 2
specContext.localMods().size() == 0
specContext.modDependenciesCompileRuntime().size() == 0
specContext.modDependenciesCompileRuntimeClient().size() == 0
specContext.allMods().size() == 2
}
private void dependencies(Map<Object, List<Path>> files) {
configureDependencies(files.implementation, this.implementation)
configureDependencies(files.runtimeOnly, this.runtimeOnly)
configureDependencies(files.compileOnly, this.compileOnly)
runtimeArtifacts[this.implementation].addAll(files.implementation)
runtimeArtifacts[this.runtimeOnly].addAll(files.runtimeOnly)
apiArtifacts[this.implementation].addAll(files.implementation)
apiArtifacts[this.compileOnly].addAll(files.compileOnly)
}
private static void configureDependencies(List<Path> files, RemapConfigurationSettings settings) {
def configuration = mock(Configuration.class)
when(configuration.resolve()).thenReturn(files*.toFile() as Set)
def provider = mock(NamedDomainObjectProvider.class)
when(provider.get()).thenReturn(configuration)
when(settings.getSourceConfiguration()).thenReturn(provider)
}
private Path mod(String modId) {
def zip = tempDir.resolve("${modId}.zip")
def spec = project.objects.newInstance(FabricModJsonV1Spec.class)
spec.modId.set(modId)
spec.version.set("1.0.0")
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
ZipUtils.add(zip, "fabric.mod.json", json)
return zip
}
private RemapConfigurationSettings createConfigurationSettings(String name) {
def settings = project.getObjects().newInstance(RemapConfigurationSettings.class, name)
settings.applyDependencyTransforms.set(true)
runtimeArtifacts.put(settings, [])
apiArtifacts.put(settings, [])
return settings
}
@CompileStatic
private static Function<RemapConfigurationSettings, Stream<Path>> resolve(Map<RemapConfigurationSettings, List<Path>> artifacts) {
return { settings ->
def paths = artifacts.get(settings)
return paths != null ? paths.stream() : Stream.empty()
}
}
}

View File

@@ -25,6 +25,7 @@
package net.fabricmc.loom.test.util
import org.gradle.api.Project
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.artifacts.dsl.RepositoryHandler
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
@@ -47,6 +48,7 @@ import net.fabricmc.loom.util.download.Download
import static org.mockito.ArgumentMatchers.any
import static org.mockito.Mockito.*
import static org.mockito.Mockito.mock
class GradleTestUtil {
static <T> Property<T> mockProperty(T value) {
@@ -64,17 +66,19 @@ class GradleTestUtil {
}
static Project mockProject() {
def mock = mock(Project.class)
def serviceRegistry = TestServiceFactory.createServiceRegistry(mock)
def project = mock(Project.class)
def serviceRegistry = TestServiceFactory.createServiceRegistry(project)
def objectFactory = serviceRegistry.get(ObjectFactory)
def providerFactory = serviceRegistry.get(ProviderFactory)
def extensions = mockExtensionContainer()
when(mock.getExtensions()).thenReturn(extensions)
when(mock.getObjects()).thenReturn(objectFactory)
when(mock.provider(any())).thenAnswer {
def configurationContainer = mock(ConfigurationContainer.class)
when(project.getExtensions()).thenReturn(extensions)
when(project.getObjects()).thenReturn(objectFactory)
when(project.provider(any())).thenAnswer {
providerFactory.provider(it.getArgument(0))
}
return mock
when(project.getConfigurations()).thenReturn(configurationContainer)
return project
}
static ExtensionContainer mockExtensionContainer() {