Still looking for help with writing plugin using extension object, along with Spock tests for such

I posted about this [earlier] (http://forums.gradle.org/gradle/topics/spock-test-for-plugin-verify-plugin-initializes-data-from-specific-extension-object), but I didn’t get any responses.

I’m writing my first plugin, along with my first Spock tests.

For background, the plugin will define a single task, which will read a set of input files from a defined location and use a defined set of “code generator” classes to generate some java code that will be written to defined locations and then eventually compiled.

I expect a build script to apply the plugin and define the extension block with property settings, and the plugin should do everything else.

Up to this point, my plugin is a skeleton, defining an empty task with some instance variables, and an extension object for configuration. I have a single spec method that successfully verifies the task is registered with the project.

I then wanted to write a spec method that verifies that for a particular set of properties set in my extension object, that the task is created with expected values. This is where I’m starting to get confused.

This is my plugin class so far:

class YangPlugin implements Plugin<Project> {
 public static final YANG_PLUGIN
 = 'yang'
 public static final YANG_GENERATE_TASK = 'yangGenerate'
    @Override
 public void apply(Project project) {
  project.plugins.apply(JavaPlugin)
  YangExtension yang = project.extensions.create(YANG_PLUGIN, YangExtension)
  project.task(YANG_GENERATE_TASK, type: YangGenerateTask) {
   it.sourceDir = yang.yangFilesRootDir
   it.generators = yang.generators
  }
 }
}

My extension object, along with one other POGO:

class YangExtension {
 CodeGenerator[] generators
 String
 yangFilesRootDir
 String[]
excludeFiles
 boolean
 inspectDependencies
}
  class CodeGenerator {
 String generatorClassName
 File outputBaseDir
 Map
additionalConfiguration
}

The task class:

class YangGenerateTask extends DefaultTask {
 CodeGenerator[] generators
    @InputDirectory
 File sourceDir
    @Input
 Class<?>[] getClasses() {
  generators.collect { it.generatorClass }
 }
    @OutputDirectories
 File[] getOutputDirectories() {
  generators.collect { it.outputBaseDir }
 }
    @TaskAction
 void generate() {
     }
}

And finally, my Spec:

class YangPluginSpec extends Specification {
    def "Yang plugin registers YangGenerate task and no others"() {
  given:
  Project project = ProjectBuilder.builder().build()
      when: "plugin is applied to project"
  project.apply(plugin: YangPlugin)
      then: "Yang task is registered"
  project.tasks.withType(YangGenerateTask).collect { it.name }.sort() == ["yangGenerate"]
 }
   def "plugin copies data from extension object"() {
  given:
  Project project = ProjectBuilder.builder().build()
  YangPlugin yangPlugin = new YangPlugin()
      when: "plugin is applied to project"
  YangExtension yang = project.extensions.create("yang", YangExtension)
  yangPlugin.apply(project)
  //project.apply(plugin: YangPlugin)
      then: "Yang task gets sourcedir set from extension object"
  project.yangGenerate.sourceDir == "/abcx"
 }
}

The first method is fine, and the second method is where I’m stuck. It’s current state is probably horribly wrong, but I’m not sure what to do here. My assertion at the end is intended to verify the property in the task is the same as what I would set in the extension object. My intention is to define the extension object that would be used and then somehow apply that to the plugin.

Have you tried…

def "Yang plugin registers YangGenerate task and no others"() {
        given:
        Project project = ProjectBuilder.builder().build()
        when: "plugin is applied to project"
        project.apply(plugin: YangPlugin)
        project.yang.yangFilesRootDir = "/abcx"
        then: "Yang task gets sourcedir set from extension object"
        project.yangGenerate.sourceDir == "/abcx"
    }

The problem you’ll have is that your configuration block for the task runs before you can configure your ‘yang’ extension. You can fix this by defining the task as you are and then configuring it in a project.afterEvaluate{}. That hook runs after the build script has been evaluated.

Ok, so you’re suggesting I change my plugin.apply() to this?:

public void apply(Project project) {
  project.plugins.apply(JavaPlugin)
  YangExtension yang = project.extensions.create(YANG_PLUGIN, YangExtension)
  project.afterEvaluate {
   project.task(YANG_GENERATE_TASK, type: YangGenerateTask) {
    it.sourceDir = yang.yangFilesRootDir
    it.generators = yang.generators
   }
  }
 }

Which is wrapping the “project.task” call within “afterEvaluate()”.

So now how do I change my spec so that my assertions run after this wrapped block is executed? Now both of my tests fail, because by the time the “when” executes, the task isn’t even created.

Update:

For instance, a simple-minded idea might be to do this:

def "Yang plugin registers YangGenerate task and no others"() {
  given:
  Project project = ProjectBuilder.builder().build()
      when: "plugin is applied to project"
  project.apply(plugin: YangPlugin)
      then: "Yang task is registered"
  project.afterEvaluate {
   project.tasks.withType(YangGenerateTask).collect { it.name }.sort() == ["yangGenerate"]
  }
 }

But this doesn’t work, because the “then” block completes before it executes the “afterEvaluate” block (if it even ever does execute it).

David maybe check our 4financeIT Uptodate plugin - https://github.com/4finance/uptodate-gradle-plugin/blob/master/src/main/groovy/com/ofg/uptodate/UptodatePlugin.groovy . There we’ve managed to successfuly register the extension.

Also you can check out the test code: https://github.com/4finance/uptodate-gradle-plugin/blob/master/src/test/groovy/com/ofg/uptodate/UptodatePluginSpec.groovy

Maybe this will help you?

Thanks, Marcin. I’ve actually been going through all the plugins in the portal one by one (haven’t gotten that far), looking for a pattern similar to what I’m doing. I noticed the plugin you mentioned and thought for a minute it was a match, but you’re checking for behaviors when executing the task. I’m trying to verify the properties set on the task right after it’s created by the plugin.

My plugin defines the extension, then creates the task with properties from the extension object. In my test, I’m simply trying to verify that the properties of the created task match the values in a given extension object. I’m starting to think this is impossible to do.

Ah! I finally found something useful digging through the plugins in the portal.

I took Sterling’s earlier advice about setting the task properties in an “afterEvaluate()” block, but I was stumped trying to figure out how my test could then check the properties after evaluation was done.

I finally noticed this test that simply calls “project.evaluate()” and then checks the state that should have happened after evaluation.

My test now works. Is this a valid strategy?