On 11/10/2011, at 12:17 AM, Adam Murdoch wrote:
>
> On 09/10/2011, at 10:39 PM, Hans Dockter wrote:
>
>> A common use case in Gradle is that you want to have different
>> variations of task behavior. Our goal is in general to avoid the use
>> of multiple actions per task to achieve this. In any case, let's look
>> at an example for variations: You want a test task that runs with and
>> without coverage. This variation does often not affect the behavior of
>> the executed task but rather the wiring.
>
> Why would you want to run the tests without coverage?
>
> One possibility I can think of is for performance reasons: if the coverage
> report is not required for this build, then don't bother running the tests
> against the instrumented production classes, as this is a touch faster.
>
> I'd say the solution to this use case is not to have different variants of
> the test task. Instead, it is to have some logic in the coverage plugin so
> that if the coverage report needs to be built, then the tests are run against
> the instrumented classes (with the appropriate wiring to make this happen),
> and if the coverage report does not need to be built, then the tests are run
> against the non-instrumented classes.
This seems right, and conceptually simpler to understand.
> That is, whether or not coverage is included is a function of the desired
> outputs of the build, not based on how the build happened to be launched.
How do we currently specify the output? I don't think we really do.
This is an interesting idea more generally to think about… instead of telling
the build system what to do (i.e. what tasks to run) telling it what to build
and it working it out.
>> For example after a coverage
>> plugin is applied, the test task depends on instrumentation and is
>> finalized by the coverage report generation (finalizer is a not yet
>> implemented concept). Or you might have two variations of an integTest
>> task. One is running just the test, the other is starting and shutting
>> down Jetty.
>
> Again, why would you want to sometimes run the setup for the tests, and
> sometimes not?
>
> The only option I can think of is that sometimes the setup is done outside
> the current build, either manually or by another build. I wonder in this case
> whether this is actually 2 different test suites, with separate results,
> rather than 2 variants of the same test task.
>
> I think the solution to this use case is to explicitly model the test suite
> as a domain object, and in particular model the test setup. The integTest
> task would depend on integTestSuite.classes and integTestSuite.classpath, it
> would produce integTestSuite.testResults, it would be initialised by
> integTestSuite.setup, and would be finalised by integTestSuite.tearDown. Both
> testSuite.setup and testSuite.tearDown would either be some buildable domain
> object, or possibly just a set of tasks.
>
> This would allow you to either model 2 integ test suites, one with setup and
> one without, or a single test suite, where you can tweak the setup and/or
> teardown.
The use of Buildable here seems right, but a conceptual stretch. I think we
might get into troubles trying to use tasks here to do the setup/teardown. We
might also need arbitrary actions.
>> One approach would be to use marker tasks that depend on the actual
>> task. For example a task integTestWithJetty that depends on Jetty. The
>> execution of integTestWithJetty would trigger a different wiring of
>> the the integTest task: runJetty < integTest < stopJetty <
>> integTestAll. You could play the same game for a test and
>> testNoCoverage task. It does not feel fully correct to use marker
>> tasks like this. To have both integTest and integTestAll in the output
>> feels strange. Those are not lifecycle tasks rather variations of test
>> and integTest. What about introducing the concept of a task variation?
>> Something like:
>>
>> task integTest(type: Test) {
>> variation {
>> integTestAll {
>> dependsOn jettyRun
>> finalizedBy jettyStop
>> }
>> }
>> }
>>
>> or
>>
>> task integTest(type: Test) {
>> variation {
>> all {
>> dependsOn jettyRun
>> finalizedBy jettyStop
>> }
>> }
>> }
>>
>> They could be executed either as integTestAll or integTest --all. They
>> would have the same actions as the actual tasks. They just differ in
>> the wiring.
>>
>> Thoughts?
>
> I'm not that excited by the idea of variants. I think it's the wrong
> direction to go as a general strategy. The basic problem is this: what
> happens when I need to execute both variants in the same build?
>
> Certainly for the example use cases you gave, I think a better solution is to
> introduce domain objects that describe what those variants produce (ie a
> coverage report, or test suites with setup and teardown). This way, by using
> domain objects, we solve the problem in terms of what the build should
> produce. Using task variants instead attempts to solve the problem in terms
> of how the build works. Describing 'what' always wins over describing 'how',
> I think.
I agree with Adam here. I don't think we should go down this road.
One thing I am wondering about is if we are missing the concept of a
“resource”. I have something like jetty in mind here. Using tasks to start/stop
the container has always bothered me. A task needs a resource, which may
require setup, configuration, teardown. I am very reluctant to add another
concept, but I think this is better than abusing the idea of tasks.
--
Luke Daley
Principal Engineer, Gradleware
http://gradleware.com