I'm developing a Gradle custom plugin and I'm having issues on how to test it.
The plugin creates an extension to receive configuration and after evaluation (project.afterEvaluate {) creates a tasks with the received configuration, those values are #Input on tasks.
Following the documentation https://docs.gradle.org/current/userguide/custom_plugins.html to create a test for the plugin, I use the following to create the project and apply the plugin
#Before fun setup() {
project = ProjectBuilder.builder().build()
project.pluginManager.apply("my.plugin.name")
and then test that extension got created:
assertTrue(project.extensions.findByName("name") is MyConfigType)
and the task got created:
assertTrue(project.tasks.findByName("mytask") is MyTaskType)
The issue I'm having is that the task is only created afterEvaluate, so this test is failing. As far as I understood, it has to be afterEvaluate so that it can receive the configuration values.
So the only way I could see if I could on the test force this project to be evaluated, but how?
Is there maybe a different way to receive values?
I posted my similar question in the gradle forums and was able to solve the issue:
https://discuss.gradle.org/t/unit-test-plugins-afterevaulate/37437/3
Apparently afterEvaluate is not the best/right place to perform the task creation. If you have a DomainObjectCollection in your extension and want to create a task for each element in the collection, task creation should be done in the all-Callback of the collection:
final MyExtension extension = project.getExtensions().create("extension", MyExtension.class);
extension.configurations.all((c) -> {
// register task here
});
If you have simple properties in the extension that are feed to the task as input, you should use lazy configuration:
public class MyExtension {
public final Property<String> property;
public final NamedDomainObjectContainer<Configuration> configurations;
#Inject
public MyExtension(final ObjectFactory objectFactory) {
property = objectFactory.property(String.class).convention("value");
configurations = objectFactory.domainObjectContainer(Configuration.class);
}
}
public abstract class MyTask extends DefaultTask {
#Input
private final Property<String> property = getProject().getObjects().property(String.class);
public Property<String> getProperty() {
return property;
}
}
And the apply method:
public class MyPlugin implements Plugin<Project> {
#Override
public void apply(final Project aProject) {
final MyExtension extension = aProject.getExtensions().create("extension", MyExtension.class);
aProject.getTasks().register("myTask", MyTask.class).configure((t) -> {
t.getProperty().set(extension.property);
});
}
}
You should call project.evaluationDependsOn(":"):
#Before fun setup() {
project = ProjectBuilder.builder().build()
project.pluginManager.apply("my.plugin.name")
project.evaluationDependsOn(":") // <<--
...
}
It executes your afterEvaluate callback.
Related
I am using XUnit and need to perform some action before running a test suit. so, I try to use IClassFixture feature of XUnit. but I cannot find a way to inject dependencies into the Fixture class. my code structure is such as below:
public class MyFixture
{
IDependency _dep;
public MyFixture(IDependency dep)
{
_dep = dep;
}
void DoSomeJob()
{
//// some code there
dep.DoSome();
}
}
And this is my test class code:
public class MyTest : IClassFixture<MyFixture>
{
[Fact]
public void test_my_code()
{
////simply just test the code
}
}
but when I run the test I am getting the exception
Xunit.Sdk.TestClassException Class fixture type 'MyFixture' had one or more unresolved constructor
Your Fixture class depends on IDependency dep, which has not been configured. You could use the Fixture class to setup a service provider; However it is not the best solution, as you have to end up using service locator patter such as
serviceProvider.GetRequiredService<T>()
Suggest to use xunit.di, it is an extension built into xunit framework to support constructor dependency injection, which allows us to achieve Inversion of Control (IoC) between test classes and their dependencies.
Install-Package Xunit.Di
To use xunit.di:
Install the xunit.di nuget package
Create a Setup.cs class to configure dependencies, (optional) and inherits the Xunit.Di.Setup.cs
Configure dependencies in the Setup.cs class.
Find full instructions and demos from xunit.di GET-STARTED
Your test project has the following:
Setup class that has a public IServiceProvider, which configures all the dependencies
Test class with constructor injecting the dependencies
Your Setup.cs class looks like below:
private IServiceProvider _services;
private bool _built = false;
private readonly IHostBuilder _defaultBuilder;
public Setup()
{
_defaultBuilder = Host.CreateDefaultBuilder();
}
public IServiceProvider Services => _services ?? Build();
private IServiceProvider Build()
{
if (_built)
throw new InvalidOperationException("Build can only be called once.");
_built = true;
_defaultBuilder.ConfigureServices((context, services) =>
{
services.AddSingleton<TextReaderService>();
services.AddSingleton<IDependency, DependencyImpl>();
// where DependencyImpl implements IDependency
// ... add other services needed
});
_services = _defaultBuilder.Build().Services;
return _services;
}
Then your test class looks like below:
public class MyTest
{
private readonly IDependency _dependency;
public MyTest(IDependency dependency)
{
_dependency = dependency;
}
[Fact]
public void test_my_code()
{
var result = _dependency.DoStuff();
Assert.NotNull(result);
////simply just test the code
}
}
Suppose I have a custom Gradle task that uses ExecOperations.javaexec() in its code:
public class MyTask extends DefaultTask {
private final ExecOperations execOps
#Inject
public MyTask(ExecOperations execOps) {
this.execOps = execOps;
}
#TaskAction
public void run() {
ExecResult result = execOps.javaexec(spec -> {
// classpath is set to some jar with a main class
spec.classpath(...);
spec.args(...);
});
// Do more stuff with result
}
}
The issue I have is that I am not sure how I can create a Spock test fixture for it to validate that the jar is being run with the correct arguments without actually running the jar itself. (The jar makes remote calls, for example, which I want to avoid).
It seems like the only plausible way forward is to potentially find a way to expose the ExecOperations for testing and possibly replace it with a stub, along with turning the Action<JavaExecSpec> from a mere lambda to something more substantial. Any ideas would be appreciated.
For context, I'm attempting to write a plugin which will generate source code (Java classes), and it exposes multiple tasks (for different purposes). These tasks rely on information given by the user via extensions. Without this user defined information, these tasks wouldn't know what to do, and so they shouldn't be run at all if the extension hasn't been defined in build.gradle by the user.
One key thing to note is that, since I'm generating source code, I'm making the JavaCompile task depend on each of these tasks. However, I only want to define this relationship if the extension has been configured in build.gradle. How do I achieve this?
The following is a simple example for one task.
The MyPlugin class which is the implementation class:
public class MyPlugin implements Plugin<Project> {
// define extensions
MyPluginConfig myPluginConfig = project.getExtensions().create("myPluginConfig", MyPluginConfig.class);
((ExtensionAware) myPluginConfig).getExtensions().create("fileConfig", FileConfig.class);
// register task
TaskProvider<MyPluginTask> taskProvider = project.getTasks().register("myPluginTask", MyPluginTask.class, task -> {
task.getFilesConfigs().set(myPluginConfig.getFileConfigs());
});
// ensure JavaCompile task depends on this plugin's task
project.getTasks().withType(JavaCompile.class).configureEach(task -> task.dependsOn(taskProvider));
}
MyPluginConfig is an extension class which just contains a list of FileConfig:
public abstract class MyPluginConfig {
public abstract ListProperty<FileConfig> getFileConfigs();
}
FileConfig is an extension class (only used within MyPluginConfig) which contains the actual information the task may use to generate Java classes:
public abstract class FileConfig {
public abstract Property<String> getX();
public abstract Property<String> getY();
public abstract Property<String> getZ();
}
And finally, MyPluginTask uses the information from these extensions to generate the Java classes:
public abstract class MyPluginTask {
#Input
public abstract ListProperty<FileConfig> getFileConfigs();
#TaskAction
public void execute() {
System.out.println("Executing task...");
}
}
In build.gradle, a user can now use this plugin like so:
plugins {
id 'com.example.my-plugin' version '0.0.1'
}
/*
myPluginConfig {
fileConfigs = [
fileConfig {
x = 'x'
y = 'y'
z = 'z'
},
fileConfig {
x = '2x'
y = '2y'
z = '2z'
},
]
}
*/
Notice that I actually commented out the myPluginConfig configuration above. As such, I don't want my task (i.e, MyPluginTask#execute) to run at all. With the way I've implemented it so far, it will run because of the dependency I created between it and the JavaCompile task.
An easy way to work around this would be to check if MyPluginTask#getFileConfigs returns an empty list. However, this doesn't feel "clean"? It seems a bit fragile since the business logic within the task could change in the future. Is there an official or proper way to do what I'm trying to achieve?
How do I create a nested parameter structure in a custom gradle plugin?
For starters, I am using Gradle 7.2. I want to make an expressive DSL-like structure for my plugin configuration, with a nested element
fileDiff {
file1 = file('${testFile1.getName()}')
file2 = file('${testFile2.getName()}')
messages {
message1 = 'Hi there'
}
}
While learning how to write Gradle Plugins I have been following the gradle plugin implementation docs, and they are great at showing what to do with the extension but not the "plugin" class.
So I have modeled my extension, FileDiffExtension like so
abstract class FileDiffExtension {
abstract RegularFileProperty getFile1()
abstract RegularFileProperty getFile2()
#Nested
abstract Messages getMessages()
void messages(Action<? super Messages> action) {
action.execute(getMessages())
}
}
And the nested Messages class is modeled as such
abstract class Messages {
abstract Property<String> getMessage1()
}
I think I am good up to this point. Then I need to pull my extension into my plugin and this is where I believe I am running into issues. My Plugin class currently looks like
class FileDiffPlugin implements Plugin<Project> {
#Override
void apply(Project project) {
project.tasks.register('fileDiff', FileDiffTask) {
project.extensions.create('fileDiff', FileDiffExtension)
project.fileDiff.extensions.create("messages", FileDiffExtension.messages)
file1 = project.fileDiff.file1
file2 = project.fileDiff.file2
messages = project.fileDiff.getMessages
}
}
}
I am trying to create a messages extension off of the root level extension fileDiff. Or maybe I am not supposed to set the messages object in the task to the getMessages() abstract method. But I have tried every combination I can think of. The actual task is shown below, but I don't think the problem lies here.
abstract class FileDiffTask extends DefaultTask {
#InputFile
abstract RegularFileProperty getFile1()
#InputFile
abstract RegularFileProperty getFile2()
#Input
abstract Property<Messages> getMessages()
#OutputFile
abstract RegularFileProperty getResultFile()
FileDiffTask() {
resultFile.convention(project.layout.buildDirectory.file('diff-result.txt'))
}
#TaskAction
def diff() {
// Print out the message
println messages.get().message1.toString()
// Now we do some fun file stuff
String diffResult
if (size(file1) == size(file2)) {
diffResult = "Files have the same size at ${file1.get().asFile.getBytes()} bytes}"
} else {
File largestFile = size(file1) > size(file2) ? file1.get().asFile : file2.get().asFile
diffResult = "${largestFile.toString()} is the largest file at ${largestFile.size()} bytes"
}
resultFile.get().asFile.write diffResult
println "File written to $resultFile"
println diffResult
}
private static long size(RegularFileProperty regularFileProperty) {
return regularFileProperty.get().asFile.size()
}
}
To test I am using the gradle test kit, and I'm currently getting the following error.
`
Could not create task ':fileDiff'.
No such property: messages for class: com.robschwartz.plugins.filediff.FileDiffExtension
Possible solutions: messages
`
Hm, according to docs you do not need to do anything with the sub-extension if it's marked as #Nested, it should just work. Have you tried to remove this line entirely?
project.fileDiff.extensions.create("messages", FileDiffExtension.messages)
I'm working on a Gradle plugin that has a Custom Task with two member variables that I would like to initialize using an extension class. I am extending the plugin class as follows:
class CustomPlugin implements Plugin<Project> {
#Override void apply(Project project) {
def extension = project.extensions.create("Custom", CustomExtension)
project.task("doTask", type: CustomTask, {
group = "Awesome"
description = "Runs a custom routine"
filePath = extension.filePath
name = extension.name
})
}
}
Is this the proper way to initialize a Task that extends DefaultTask? I'm trying to understand if initialization is done in CustomPlugin or if within the CustomTask one would use:
#TaskAction void removeUnusedResources() {
String filePath = project.Custom.lintXmlFilePath
String name = project.Custom.ignoreResFiles
// Proceed with the task action
}
Only the second approach seems to work for me. One common pattern I am noticing in other plugins is that tasks tend to be defined within the plugin using project.task("taskName") << { // task actions here} instead of creating a separate class that extends DefaultTask. What is the right convention and where can I find more information?